From 6d7730ffd17da52a61c563a98066156bb8a4e99e Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Wed, 30 Aug 2017 17:55:42 +0300 Subject: [PATCH] EDU-1146 Remove educational plugins sources from intellij-community repo --- .idea/modules.xml | 2 - .../build/CommunityRepositoryModules.groovy | 4 - python/educational-core/build.gradle | 74 - python/educational-core/educational-core.iml | 38 - python/educational-core/gradle.properties | 6 - .../gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - python/educational-core/gradlew | 160 - python/educational-core/gradlew.bat | 90 - .../lib/twitter4j-core-4.0.4.jar | Bin 290456 -> 0 bytes .../META-INF/android-dependencies.xml | 9 - .../resources/META-INF/edu-intellij.xml | 17 - .../resources/META-INF/plugin.xml | 209 - .../code-mirror/codemirror-darcula.css | 367 - .../code-mirror/codemirror-old-darcula.css | 147 - .../resources/code-mirror/codemirror-old.css | 147 - .../resources/code-mirror/codemirror.css | 351 - .../resources/code-mirror/codemirror.js | 8892 ----------------- .../resources/code-mirror/colorize.js | 40 - .../resources/code-mirror/javascript.js | 742 -- .../resources/code-mirror/runmode.js | 72 - .../resources/code-mirror/template.html | 64 - .../internal/EduTestRunner.java.ft | 18 - .../fileTemplates/internal/task.html.ft | 4 - .../fileTemplates/internal/task.md.ft | 3 - .../com/jetbrains/edu/learning/CheckTask.png | Bin 411 -> 0 bytes .../jetbrains/edu/learning/CheckTask@2x.png | Bin 1064 -> 0 bytes .../com/jetbrains/edu/learning/Course.png | Bin 402 -> 0 bytes .../com/jetbrains/edu/learning/Course@2x.png | Bin 908 -> 0 bytes .../jetbrains/edu/learning/Course@2x_dark.png | Bin 939 -> 0 bytes .../learning/CourseCreationProjectType.png | Bin 647 -> 0 bytes .../learning/CourseCreationProjectType@2x.png | Bin 1442 -> 0 bytes .../CourseCreationProjectType@2x_dark.png | Bin 1489 -> 0 bytes .../CourseCreationProjectType_dark.png | Bin 654 -> 0 bytes .../jetbrains/edu/learning/CourseProgress.png | Bin 597 -> 0 bytes .../edu/learning/CourseProgress@2x.png | Bin 1052 -> 0 bytes .../edu/learning/CourseProgress@2x_dark.png | Bin 1052 -> 0 bytes .../edu/learning/CourseProgress_dark.png | Bin 597 -> 0 bytes .../jetbrains/edu/learning/Course_dark.png | Bin 404 -> 0 bytes .../edu/learning/EducationalProjectType.png | Bin 674 -> 0 bytes .../learning/EducationalProjectType@2x.png | Bin 1340 -> 0 bytes .../EducationalProjectType@2x_dark.png | Bin 1338 -> 0 bytes .../learning/EducationalProjectType_dark.png | Bin 674 -> 0 bytes .../com/jetbrains/edu/learning/Lesson.png | Bin 285 -> 0 bytes .../com/jetbrains/edu/learning/Lesson@2x.png | Bin 751 -> 0 bytes .../jetbrains/edu/learning/LessonCompl.png | Bin 483 -> 0 bytes .../jetbrains/edu/learning/LessonCompl@2x.png | Bin 1107 -> 0 bytes .../jetbrains/edu/learning/ResetTaskFile.png | Bin 474 -> 0 bytes .../edu/learning/ResetTaskFile@2x.png | Bin 1042 -> 0 bytes .../com/jetbrains/edu/learning/Sandbox.png | Bin 265 -> 0 bytes .../com/jetbrains/edu/learning/Sandbox@2x.png | Bin 455 -> 0 bytes .../com/jetbrains/edu/learning/Stepik.png | Bin 285 -> 0 bytes .../com/jetbrains/edu/learning/Stepik@2x.png | Bin 439 -> 0 bytes .../com/jetbrains/edu/learning/StepikOff.png | Bin 479 -> 0 bytes .../jetbrains/edu/learning/StepikOff@2x.png | Bin 1025 -> 0 bytes .../jetbrains/edu/learning/StepikRefresh.png | Bin 382 -> 0 bytes .../edu/learning/StepikRefresh@2x.png | Bin 561 -> 0 bytes .../icons/com/jetbrains/edu/learning/Task.png | Bin 160 -> 0 bytes .../com/jetbrains/edu/learning/Task@2x.png | Bin 269 -> 0 bytes .../jetbrains/edu/learning/Task@2x_dark.png | Bin 269 -> 0 bytes .../com/jetbrains/edu/learning/TaskCompl.png | Bin 369 -> 0 bytes .../jetbrains/edu/learning/TaskCompl@2x.png | Bin 822 -> 0 bytes .../edu/learning/TaskCompl@2x_dark.png | Bin 821 -> 0 bytes .../jetbrains/edu/learning/TaskCompl_dark.png | Bin 368 -> 0 bytes .../edu/learning/TaskDescription.png | Bin 196 -> 0 bytes .../edu/learning/TaskDescription@2x.png | Bin 462 -> 0 bytes .../edu/learning/TaskDescription@2x_dark.png | Bin 462 -> 0 bytes .../edu/learning/TaskDescription_dark.png | Bin 196 -> 0 bytes .../com/jetbrains/edu/learning/TaskProbl.png | Bin 336 -> 0 bytes .../jetbrains/edu/learning/TaskProbl@2x.png | Bin 738 -> 0 bytes .../edu/learning/TaskProbl@2x_dark.png | Bin 739 -> 0 bytes .../jetbrains/edu/learning/TaskProbl_dark.png | Bin 337 -> 0 bytes .../com/jetbrains/edu/learning/Task_dark.png | Bin 159 -> 0 bytes .../com/jetbrains/edu/learning/WatchInput.png | Bin 1328 -> 0 bytes .../edu/learning/interpreterGear.png | Bin 316 -> 0 bytes .../edu/learning/interpreterGear@2x.png | Bin 552 -> 0 bytes .../edu/learning/interpreterGear@2x_dark.png | Bin 628 -> 0 bytes .../edu/learning/interpreterGear_dark.png | Bin 314 -> 0 bytes .../com/jetbrains/edu/learning/showHint.png | Bin 410 -> 0 bytes .../jetbrains/edu/learning/showHint@2x.png | Bin 871 -> 0 bytes .../oauthResponsePages/errorPage.html | 25 - .../resources/oauthResponsePages/okPage.html | 25 - .../resources/style/buttons.css | 22 - .../resources/style/buttonsDarcula.css | 64 - .../resources/style/javaFXBrowser.css | 20 - .../resources/style/javaFXBrowserDarcula.css | 43 - .../style/javaFXBrowserDarculaScrollBar.css | 56 - .../style/javaFXBrowserScrollBar.css | 51 - .../CCAnswerPlaceholderActionGroup.java | 12 - .../coursecreator/CCHighlightErrorFilter.java | 25 - .../edu/coursecreator/CCProjectComponent.java | 126 - .../edu/coursecreator/CCProjectService.java | 113 - .../CCRefactoringElementListenerProvider.java | 101 - .../CCSubtaskEditorNotificationProvider.java | 276 - .../CCTestsTabTitleProvider.java | 29 - .../jetbrains/edu/coursecreator/CCUtils.java | 310 - .../coursecreator/CCVirtualFileListener.java | 136 - .../actions/CCAddAsTaskFile.java | 69 - .../actions/CCChangeCourseInfo.java | 91 - .../actions/CCCreateCourseArchive.java | 222 - .../coursecreator/actions/CCCreateLesson.java | 100 - .../actions/CCCreateStudyItemActionBase.java | 177 - .../coursecreator/actions/CCCreateTask.java | 123 - .../coursecreator/actions/CCEditHintAction.kt | 29 - .../actions/CCEditTaskTextAction.java | 81 - .../actions/CCFileActionGroup.java | 13 - .../actions/CCFromCourseArchive.java | 155 - .../actions/CCHideFromStudent.java | 95 - .../actions/CCNewSubtaskAction.java | 113 - .../actions/CCPluginToggleAction.java | 18 - .../actions/CCProjectViewActionGroup.java | 13 - .../coursecreator/actions/CCShowPreview.java | 163 - .../actions/CCSwitchSubtask.java | 62 - .../actions/CCTaskFileActionBase.java | 76 - .../placeholder/CCActivatePlaceholder.java | 51 - .../placeholder/CCAddAnswerPlaceholder.java | 158 - .../CCAnswerPlaceholderAction.java | 110 - .../CCChangePlaceholderVisibility.java | 111 - .../CCCreateAnswerPlaceholderDialog.java | 62 - .../placeholder/CCDeactivatePlaceholder.java | 49 - .../CCDeleteAllAnswerPlaceholdersAction.java | 124 - .../CCDeleteAnswerPlaceholder.java | 68 - .../placeholder/CCEditAnswerPlaceholder.java | 49 - .../CCHidePlaceholderFromPrevSubtasks.java | 27 - .../CCMakeVisibleForPrevSubtasks.java | 27 - .../CCSubtaskPlaceholderAction.java | 81 - .../actions/stepik/CCGetCourseFromStepic.java | 54 - .../actions/stepik/CCPushCourse.java | 83 - .../actions/stepik/CCPushLesson.java | 100 - .../actions/stepik/CCPushTask.java | 105 - .../handlers/CCLessonMoveHandlerDelegate.java | 137 - .../handlers/CCLessonRenameHandler.java | 30 - .../handlers/CCRenameHandler.java | 76 - .../handlers/CCTaskMoveHandlerDelegate.java | 194 - .../handlers/CCTaskRenameHandler.java | 53 - .../intellij/EduCCModuleBuilder.java | 168 - .../intellij/EduCCProjectTemplate.java | 41 - .../projectView/CCCourseDirectoryNode.java | 59 - .../projectView/CCDirectoryNode.java | 61 - .../projectView/CCLessonDirectoryNode.java | 25 - .../CCStudentInvisibleFileNode.java | 38 - .../projectView/CCTaskDirectoryNode.java | 98 - .../projectView/CCTreeStructureProvider.java | 24 - .../edu/coursecreator/settings/CCOptions.form | 76 - .../edu/coursecreator/settings/CCOptions.java | 57 - .../coursecreator/settings/CCSettings.java | 38 - .../stepik/CCStepicConnector.java | 443 - .../ui/CCCreateStudyItemDialog.java | 33 - .../ui/CCCreateStudyItemPanel.form | 42 - .../ui/CCCreateStudyItemPanel.java | 32 - .../coursecreator/ui/CCItemPositionPanel.form | 37 - .../coursecreator/ui/CCItemPositionPanel.java | 31 - .../ui/CCMoveStudyItemDialog.java | 29 - .../coursecreator/ui/CCNewProjectPanel.form | 79 - .../coursecreator/ui/CCNewProjectPanel.java | 106 - .../ui/CreateCourseArchiveDialog.java | 44 - .../ui/CreateCourseArchivePanel.form | 79 - .../ui/CreateCourseArchivePanel.java | 68 - .../edu/learning/EduPluginConfigurator.java | 174 - ...udyAnswerPlaceholderExtendWordHandler.java | 61 - .../StudyFileEditorManagerListener.java | 71 - .../edu/learning/StudyMoveDelegate.java | 55 - .../edu/learning/StudyProjectComponent.java | 382 - .../edu/learning/StudyRenameHandler.java | 32 - .../edu/learning/StudySerializationUtils.java | 776 -- .../jetbrains/edu/learning/StudySettings.java | 87 - .../jetbrains/edu/learning/StudyState.java | 53 - .../edu/learning/StudySubtaskUtils.java | 198 - .../edu/learning/StudyTabTitleProvider.java | 46 - .../edu/learning/StudyTaskManager.java | 249 - .../StudyTwitterPluginConfigurator.java | 65 - .../learning/StudyTypeHandlerDelegate.java | 133 - .../jetbrains/edu/learning/StudyUtils.java | 751 -- .../actions/StudyActionWithShortcut.java | 20 - .../actions/StudyAfterCheckAction.java | 10 - .../learning/actions/StudyCheckAction.java | 174 - .../actions/StudyEditInputAction.java | 240 - .../actions/StudyFillPlaceholdersAction.java | 67 - .../learning/actions/StudyNextTaskAction.java | 34 - .../actions/StudyNextWindowAction.java | 56 - .../actions/StudyPrevWindowAction.java | 59 - .../actions/StudyPreviousTaskAction.java | 34 - .../StudyRefreshAnswerPlaceholder.java | 88 - .../actions/StudyRefreshTaskFileAction.java | 196 - .../learning/actions/StudyShowHintAction.java | 114 - .../actions/StudySwitchTaskPanelAction.kt | 71 - .../actions/StudyTaskNavigationAction.java | 63 - .../StudyUpdateRecommendationAction.java | 76 - .../actions/StudyWindowNavigationAction.java | 59 - .../builtInServer/EduBuiltInServerUtils.java | 163 - .../builtInServer/EduStepikRestService.java | 225 - .../edu/learning/checker/OutputTaskChecker.kt | 105 - .../learning/checker/StudyCheckListener.java | 14 - .../learning/checker/StudyCheckResult.java | 21 - .../edu/learning/checker/StudyCheckUtils.java | 170 - .../learning/checker/StudyTaskChecker.java | 42 - .../checker/StudyTestsOutputParser.java | 72 - .../checker/TaskWithSubtasksChecker.java | 95 - .../EduAnswerPlaceholderDeleteHandler.java | 21 - .../core/EduAnswerPlaceholderPainter.java | 112 - .../learning/core/EduDocumentListener.java | 109 - .../jetbrains/edu/learning/core/EduNames.java | 56 - .../jetbrains/edu/learning/core/EduUtils.java | 271 - .../courseFormat/AnswerPlaceholder.java | 301 - .../AnswerPlaceholderComparator.java | 15 - .../AnswerPlaceholderSubtaskInfo.java | 110 - .../edu/learning/courseFormat/Course.java | 217 - .../edu/learning/courseFormat/Lesson.java | 156 - .../learning/courseFormat/RemoteCourse.java | 127 - .../edu/learning/courseFormat/StudyItem.java | 8 - .../learning/courseFormat/StudyStatus.java | 5 - .../edu/learning/courseFormat/TaskFile.java | 162 - .../edu/learning/courseFormat/UserTest.java | 41 - .../courseFormat/tasks/ChoiceTask.java | 88 - .../learning/courseFormat/tasks/CodeTask.java | 45 - .../learning/courseFormat/tasks/OutputTask.kt | 20 - .../courseFormat/tasks/PyCharmTask.java | 32 - .../edu/learning/courseFormat/tasks/Task.java | 326 - .../courseFormat/tasks/TaskWithSubtasks.java | 80 - .../courseFormat/tasks/TheoryTask.java | 42 - .../courseGeneration/StudyGenerator.java | 125 - .../StudyProjectGenerator.java | 219 - .../editor/StudyChoiceVariantsPanel.kt | 121 - .../edu/learning/editor/StudyEditor.java | 45 - .../editor/StudyEditorFactoryListener.java | 102 - .../editor/StudyFileEditorProvider.java | 59 - .../editor/StudyHighlightErrorFilter.java | 25 - .../intellij/EduIntelliJProjectTemplate.java | 9 - .../intellij/EduIntelliJTemplateFactory.java | 45 - .../EduIntellijApplicationComponent.kt | 16 - .../android/EduSelectCourseProjectWizard.java | 61 - .../android/EduStartLearningAction.java | 32 - .../generation/EduCourseModuleBuilder.java | 16 - .../generation/EduProjectGenerator.java | 23 - .../EduCustomCourseModuleBuilder.java | 117 - .../EduCustomCourseProjectTemplate.java | 41 - .../localCourses/EduLocalCoursePanel.form | 53 - .../localCourses/EduLocalCoursePanel.java | 51 - .../stepik/EduRemoteCourseModuleBuilder.java | 110 - .../stepik/EduRemoteCourseTemplate.java | 48 - .../learning/navigation/StudyNavigator.java | 230 - .../newproject/EduBrowseCoursesAction.java | 45 - .../newproject/EduCourseProjectGenerator.java | 47 - .../newproject/ui/EduCoursesPanel.form | 132 - .../newproject/ui/EduCoursesPanel.java | 462 - .../ui/EduCreateNewProjectDialog.java | 102 - .../ui/EduCreateNewProjectPanel.form | 71 - .../ui/EduCreateNewProjectPanel.java | 103 - .../ui/EduCreateNewStepikProjectDialog.java | 74 - .../projectView/CourseDirectoryNode.java | 69 - .../learning/projectView/DirectoryNode.java | 57 - .../projectView/LessonDirectoryNode.java | 78 - .../projectView/StudyDirectoryNode.java | 51 - .../StudyTreeStructureProvider.java | 57 - .../projectView/TaskDirectoryNode.java | 104 - .../learning/settings/StudyConfigurable.java | 68 - .../settings/StudyOptionsProvider.java | 6 - .../settings/StudyOptionsProviderEP.java | 9 - .../learning/statistics/EduStatistics.java | 43 - .../statistics/EduUsagesCollector.java | 107 - .../stepic/EduAdaptiveStepicConnector.java | 761 -- .../edu/learning/stepic/EduCourseUpdater.java | 42 - .../EduNextRecommendationCheckListener.java | 45 - .../stepic/EduStepicAuthorizedClient.java | 193 - .../edu/learning/stepic/EduStepicClient.java | 133 - .../learning/stepic/EduStepicConnector.java | 522 - .../edu/learning/stepic/EduStepicNames.java | 29 - .../edu/learning/stepic/EduStepicUpdater.java | 91 - .../edu/learning/stepic/EduStepikUtils.java | 42 - .../edu/learning/stepic/OAuthDialog.java | 75 - .../stepic/StepicAdaptiveReactionsPanel.java | 180 - .../learning/stepic/StepicStudyOptions.form | 67 - .../learning/stepic/StepicStudyOptions.java | 172 - .../edu/learning/stepic/StepicUser.java | 120 - .../edu/learning/stepic/StepicWrappers.java | 540 - .../learning/twitter/StudyTwitterAction.java | 28 - .../learning/twitter/StudyTwitterUtils.java | 295 - .../edu/learning/ui/AuthorizationPanel.form | 39 - .../edu/learning/ui/AuthorizationPanel.java | 22 - .../ui/CCCreateAnswerPlaceholderPanel.form | 106 - .../ui/CCCreateAnswerPlaceholderPanel.java | 222 - .../edu/learning/ui/StudyBrowserWindow.java | 363 - .../edu/learning/ui/StudyCondition.java | 15 - .../jetbrains/edu/learning/ui/StudyHint.kt | 96 - .../learning/ui/StudyJavaFxToolWindow.java | 52 - .../edu/learning/ui/StudyNewProjectPanel.java | 354 - .../edu/learning/ui/StudyProgressBar.java | 107 - .../learning/ui/StudyStepicUserWidget.java | 189 - .../edu/learning/ui/StudySwingToolWindow.java | 69 - .../learning/ui/StudyTestContentPanel.java | 71 - .../ui/StudyTestResultsToolWindowFactory.kt | 48 - .../edu/learning/ui/StudyToolWindow.java | 349 - .../learning/ui/StudyToolWindowFactory.java | 40 - .../src/icons/EducationalCoreIcons.java | 35 - .../deletePlaceholder_after.txt | 1 - .../deletePlaceholder_before.txt | 1 - .../addPlaceholder/onePlaceholder_after.txt | 1 - .../addPlaceholder/onePlaceholder_before.txt | 1 - .../placeholderIntersection.txt | 1 - .../addPlaceholder/withoutSelection_after.txt | 3 - .../withoutSelection_before.txt | 3 - .../actions/preview/noplaceholders.txt | 1 - .../actions/preview/several_after.txt | 1 - .../actions/preview/several_before.txt | 1 - .../testData/actions/preview/test_after.txt | 1 - .../testData/actions/preview/test_before.txt | 1 - .../actions/taskFileActions/nonTaskFile.txt | 1 - .../actions/taskFileActions/taskFile.txt | 1 - .../testData/migration/adaptive45.after.xml | 154 - .../testData/migration/adaptive45.xml | 175 - .../migration/fromThirdToForth.after.xml | 131 - .../testData/migration/fromThirdToForth.xml | 110 - .../testData/migration/subtasks45.after.xml | 145 - .../testData/migration/subtasks45.xml | 158 - .../testData/migration/theory351To4.after.xml | 115 - .../testData/migration/theory351To4.xml | 117 - .../testData/migration/theory35To4.after.xml | 84 - .../testData/migration/theory35To4.xml | 87 - .../educational-core/testData/stepic/1.json | 72 - .../educational-core/testData/stepic/2.json | 78 - .../educational-core/testData/stepic/3.json | 86 - .../testData/stepic/courses.json | 330 - .../testData/stepic/placeholder.json | 28 - .../edu/coursecreator/CCTestCase.java | 211 - .../edu/coursecreator/CCTestsUtil.java | 37 - .../CCAnswerPlaceholderActionTest.java | 77 - .../actions/CCShowPreviewTest.java | 78 - .../actions/CCTaskFileActionTest.java | 57 - .../edu/learning/StudyMigrationTest.java | 71 - .../stepic/StudyStepicFormatTest.java | 101 - .../Edu-Python/Edu-Python.iml | 19 - .../Edu-Python/resources/META-INF/plugin.xml | 68 - .../com/jetbrains/python/edu/user_tester.py | 58 - .../courses/Introduction to Python.zip | Bin 20206 -> 0 bytes .../PyStudyInterpreterInspection.html | 6 - .../Edu-Python/resources/python.js | 348 - .../CreateFromArchiveProjectStep.java | 64 - .../coursecreator/PyCCChangeCourseInfo.java | 51 - .../coursecreator/PyCCCreateCourseAction.java | 34 - .../PyCCCreateCourseProjectStep.java | 30 - .../PyCCCreateProjectFromArchive.java | 39 - .../coursecreator/PyCCProjectGenerator.java | 138 - .../run/PyCCCommandLineState.java | 114 - .../run/PyCCRunTestConfiguration.java | 80 - .../run/PyCCRunTestsConfigurationFactory.java | 19 - .../PyCCRunTestsConfigurationProducer.java | 106 - .../run/PyCCRunTestsConfigurationType.java | 41 - .../coursecreator/run/PyCCSettingEditor.form | 34 - .../coursecreator/run/PyCCSettingsEditor.java | 69 - .../edu/learning/PyEduPluginConfigurator.java | 211 - .../PyStudyDirectoryProjectGenerator.java | 389 - .../edu/learning/PyStudyImportResolver.java | 30 - .../learning/PyStudyInitialConfigurator.java | 33 - .../PyStudyInterpreterInspection.java | 125 - .../PyStudyLanguageLevelInspection.kt | 76 - .../edu/learning/PyStudyShowTutorial.java | 41 - .../actions/PyStudyWatchTutorialAction.java | 17 - .../learning/checker/PyStudySmartChecker.java | 144 - .../learning/checker/PyStudyTaskChecker.java | 123 - .../learning/checker/PyStudyTestRunner.java | 65 - .../PyStudyInspectionExtension.java | 36 - python/educational-python/build.gradle | 119 - .../educational-python/build/plugin-list.txt | 2 - .../educational-python/educational-python.iml | 2 +- python/educational-python/gradle.properties | 6 - .../gradle/wrapper/gradle-wrapper.jar | Bin 53636 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - python/educational-python/gradlew | 160 - python/educational-python/gradlew.bat | 90 - .../educational-python/main_pycharm_edu.iml | 1 - .../fileTemplates/internal/task.py.ft | 1 - .../fileTemplates/internal/test_helper.py.ft | 224 - .../fileTemplates/internal/tests.py.ft | 17 - python/educational-python/settings.gradle | 3 - .../python/PyDocumentationManager.java | 42 - 375 files changed, 1 insertion(+), 40243 deletions(-) delete mode 100644 python/educational-core/build.gradle delete mode 100644 python/educational-core/educational-core.iml delete mode 100644 python/educational-core/gradle.properties delete mode 100644 python/educational-core/gradle/wrapper/gradle-wrapper.jar delete mode 100644 python/educational-core/gradle/wrapper/gradle-wrapper.properties delete mode 100755 python/educational-core/gradlew delete mode 100644 python/educational-core/gradlew.bat delete mode 100644 python/educational-core/lib/twitter4j-core-4.0.4.jar delete mode 100644 python/educational-core/resources/META-INF/android-dependencies.xml delete mode 100644 python/educational-core/resources/META-INF/edu-intellij.xml delete mode 100644 python/educational-core/resources/META-INF/plugin.xml delete mode 100644 python/educational-core/resources/code-mirror/codemirror-darcula.css delete mode 100644 python/educational-core/resources/code-mirror/codemirror-old-darcula.css delete mode 100644 python/educational-core/resources/code-mirror/codemirror-old.css delete mode 100644 python/educational-core/resources/code-mirror/codemirror.css delete mode 100644 python/educational-core/resources/code-mirror/codemirror.js delete mode 100644 python/educational-core/resources/code-mirror/colorize.js delete mode 100644 python/educational-core/resources/code-mirror/javascript.js delete mode 100644 python/educational-core/resources/code-mirror/runmode.js delete mode 100644 python/educational-core/resources/code-mirror/template.html delete mode 100644 python/educational-core/resources/fileTemplates/internal/EduTestRunner.java.ft delete mode 100644 python/educational-core/resources/fileTemplates/internal/task.html.ft delete mode 100644 python/educational-core/resources/fileTemplates/internal/task.md.ft delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/CheckTask.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/CheckTask@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Course.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Course@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Course@2x_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseCreationProjectType.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseCreationProjectType@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseCreationProjectType@2x_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseCreationProjectType_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseProgress.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseProgress@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseProgress@2x_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseProgress_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Course_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/EducationalProjectType.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/EducationalProjectType@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/EducationalProjectType@2x_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/EducationalProjectType_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Lesson.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Lesson@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/LessonCompl.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/LessonCompl@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/ResetTaskFile.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/ResetTaskFile@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Sandbox.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Sandbox@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Stepik.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Stepik@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/StepikOff.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/StepikOff@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/StepikRefresh.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/StepikRefresh@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Task.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Task@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Task@2x_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskCompl.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskCompl@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskCompl@2x_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskCompl_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskDescription.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskDescription@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskDescription@2x_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskDescription_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskProbl.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskProbl@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskProbl@2x_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskProbl_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/Task_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/WatchInput.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/interpreterGear.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/interpreterGear@2x.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/interpreterGear@2x_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/interpreterGear_dark.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/showHint.png delete mode 100644 python/educational-core/resources/icons/com/jetbrains/edu/learning/showHint@2x.png delete mode 100644 python/educational-core/resources/oauthResponsePages/errorPage.html delete mode 100644 python/educational-core/resources/oauthResponsePages/okPage.html delete mode 100644 python/educational-core/resources/style/buttons.css delete mode 100644 python/educational-core/resources/style/buttonsDarcula.css delete mode 100644 python/educational-core/resources/style/javaFXBrowser.css delete mode 100644 python/educational-core/resources/style/javaFXBrowserDarcula.css delete mode 100644 python/educational-core/resources/style/javaFXBrowserDarculaScrollBar.css delete mode 100644 python/educational-core/resources/style/javaFXBrowserScrollBar.css delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/CCAnswerPlaceholderActionGroup.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/CCHighlightErrorFilter.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectService.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/CCSubtaskEditorNotificationProvider.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/CCTestsTabTitleProvider.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/CCUtils.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCAddAsTaskFile.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCChangeCourseInfo.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateLesson.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateStudyItemActionBase.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateTask.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCEditHintAction.kt delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCEditTaskTextAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFileActionGroup.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCNewSubtaskAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPluginToggleAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCProjectViewActionGroup.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCSwitchSubtask.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionBase.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCActivatePlaceholder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAddAnswerPlaceholder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAnswerPlaceholderAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCChangePlaceholderVisibility.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCCreateAnswerPlaceholderDialog.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeactivatePlaceholder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAllAnswerPlaceholdersAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAnswerPlaceholder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCEditAnswerPlaceholder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCHidePlaceholderFromPrevSubtasks.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCMakeVisibleForPrevSubtasks.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCSubtaskPlaceholderAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCGetCourseFromStepic.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushCourse.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushLesson.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushTask.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCLessonMoveHandlerDelegate.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCLessonRenameHandler.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCRenameHandler.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCTaskMoveHandlerDelegate.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCTaskRenameHandler.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCModuleBuilder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCProjectTemplate.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCCourseDirectoryNode.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCDirectoryNode.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCLessonDirectoryNode.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCStudentInvisibleFileNode.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCTaskDirectoryNode.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCSettings.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/stepik/CCStepicConnector.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemDialog.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemPanel.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemPanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCItemPositionPanel.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCItemPositionPanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCMoveStudyItemDialog.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCNewProjectPanel.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCNewProjectPanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchiveDialog.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchivePanel.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchivePanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyAnswerPlaceholderExtendWordHandler.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyFileEditorManagerListener.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyMoveDelegate.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyRenameHandler.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudySerializationUtils.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudySettings.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyState.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudySubtaskUtils.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyTabTitleProvider.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyTwitterPluginConfigurator.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyTypeHandlerDelegate.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyActionWithShortcut.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyAfterCheckAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyEditInputAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyNextTaskAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyPreviousTaskAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudySwitchTaskPanelAction.kt delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyUpdateRecommendationAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduBuiltInServerUtils.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduStepikRestService.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/checker/OutputTaskChecker.kt delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckListener.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckResult.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTaskChecker.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/checker/TaskWithSubtasksChecker.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderDeleteHandler.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/core/EduDocumentListener.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/core/EduUtils.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderComparator.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderSubtaskInfo.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Course.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Lesson.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/RemoteCourse.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/StudyItem.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/StudyStatus.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/UserTest.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/ChoiceTask.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/CodeTask.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/OutputTask.kt delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/PyCharmTask.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TaskWithSubtasks.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/editor/StudyChoiceVariantsPanel.kt delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/editor/StudyEditor.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/editor/StudyHighlightErrorFilter.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntelliJProjectTemplate.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntelliJTemplateFactory.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntellijApplicationComponent.kt delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/android/EduSelectCourseProjectWizard.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/android/EduStartLearningAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduCourseModuleBuilder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduProjectGenerator.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduCustomCourseModuleBuilder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduCustomCourseProjectTemplate.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduLocalCoursePanel.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduLocalCoursePanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/stepik/EduRemoteCourseModuleBuilder.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/intellij/stepik/EduRemoteCourseTemplate.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/newproject/EduBrowseCoursesAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/newproject/EduCourseProjectGenerator.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectDialog.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewStepikProjectDialog.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/projectView/CourseDirectoryNode.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/projectView/DirectoryNode.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/projectView/LessonDirectoryNode.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/projectView/TaskDirectoryNode.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/settings/StudyConfigurable.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/settings/StudyOptionsProvider.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/settings/StudyOptionsProviderEP.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/statistics/EduStatistics.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/EduCourseUpdater.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/EduNextRecommendationCheckListener.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicAuthorizedClient.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicClient.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicUpdater.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepikUtils.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/OAuthDialog.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicUser.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/twitter/StudyTwitterAction.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/AuthorizationPanel.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/AuthorizationPanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/CCCreateAnswerPlaceholderPanel.form delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/CCCreateAnswerPlaceholderPanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyBrowserWindow.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyCondition.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyHint.kt delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyProgressBar.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyStepicUserWidget.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudySwingToolWindow.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyTestContentPanel.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java delete mode 100644 python/educational-core/src/icons/EducationalCoreIcons.java delete mode 100644 python/educational-core/testData/actions/addPlaceholder/deletePlaceholder_after.txt delete mode 100644 python/educational-core/testData/actions/addPlaceholder/deletePlaceholder_before.txt delete mode 100644 python/educational-core/testData/actions/addPlaceholder/onePlaceholder_after.txt delete mode 100644 python/educational-core/testData/actions/addPlaceholder/onePlaceholder_before.txt delete mode 100644 python/educational-core/testData/actions/addPlaceholder/placeholderIntersection.txt delete mode 100644 python/educational-core/testData/actions/addPlaceholder/withoutSelection_after.txt delete mode 100644 python/educational-core/testData/actions/addPlaceholder/withoutSelection_before.txt delete mode 100644 python/educational-core/testData/actions/preview/noplaceholders.txt delete mode 100644 python/educational-core/testData/actions/preview/several_after.txt delete mode 100644 python/educational-core/testData/actions/preview/several_before.txt delete mode 100644 python/educational-core/testData/actions/preview/test_after.txt delete mode 100644 python/educational-core/testData/actions/preview/test_before.txt delete mode 100644 python/educational-core/testData/actions/taskFileActions/nonTaskFile.txt delete mode 100644 python/educational-core/testData/actions/taskFileActions/taskFile.txt delete mode 100644 python/educational-core/testData/migration/adaptive45.after.xml delete mode 100644 python/educational-core/testData/migration/adaptive45.xml delete mode 100644 python/educational-core/testData/migration/fromThirdToForth.after.xml delete mode 100644 python/educational-core/testData/migration/fromThirdToForth.xml delete mode 100644 python/educational-core/testData/migration/subtasks45.after.xml delete mode 100644 python/educational-core/testData/migration/subtasks45.xml delete mode 100644 python/educational-core/testData/migration/theory351To4.after.xml delete mode 100644 python/educational-core/testData/migration/theory351To4.xml delete mode 100644 python/educational-core/testData/migration/theory35To4.after.xml delete mode 100644 python/educational-core/testData/migration/theory35To4.xml delete mode 100644 python/educational-core/testData/stepic/1.json delete mode 100644 python/educational-core/testData/stepic/2.json delete mode 100644 python/educational-core/testData/stepic/3.json delete mode 100644 python/educational-core/testData/stepic/courses.json delete mode 100644 python/educational-core/testData/stepic/placeholder.json delete mode 100644 python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java delete mode 100644 python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestsUtil.java delete mode 100644 python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderActionTest.java delete mode 100644 python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCShowPreviewTest.java delete mode 100644 python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionTest.java delete mode 100644 python/educational-core/testSrc/com/jetbrains/edu/learning/StudyMigrationTest.java delete mode 100644 python/educational-core/testSrc/com/jetbrains/edu/learning/stepic/StudyStepicFormatTest.java delete mode 100644 python/educational-python/Edu-Python/Edu-Python.iml delete mode 100644 python/educational-python/Edu-Python/resources/META-INF/plugin.xml delete mode 100644 python/educational-python/Edu-Python/resources/com/jetbrains/python/edu/user_tester.py delete mode 100644 python/educational-python/Edu-Python/resources/courses/Introduction to Python.zip delete mode 100644 python/educational-python/Edu-Python/resources/inspectionDescriptions/PyStudyInterpreterInspection.html delete mode 100644 python/educational-python/Edu-Python/resources/python.js delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/CreateFromArchiveProjectStep.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCChangeCourseInfo.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateCourseAction.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateCourseProjectStep.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateProjectFromArchive.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCProjectGenerator.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestConfiguration.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationFactory.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationType.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingEditor.form delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingsEditor.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyImportResolver.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInitialConfigurator.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInterpreterInspection.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyLanguageLevelInspection.kt delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyShowTutorial.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyWatchTutorialAction.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudySmartChecker.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudyTaskChecker.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudyTestRunner.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/highlighting/PyStudyInspectionExtension.java delete mode 100644 python/educational-python/build.gradle delete mode 100644 python/educational-python/gradle.properties delete mode 100644 python/educational-python/gradle/wrapper/gradle-wrapper.jar delete mode 100644 python/educational-python/gradle/wrapper/gradle-wrapper.properties delete mode 100755 python/educational-python/gradlew delete mode 100644 python/educational-python/gradlew.bat delete mode 100644 python/educational-python/resources/fileTemplates/internal/task.py.ft delete mode 100644 python/educational-python/resources/fileTemplates/internal/test_helper.py.ft delete mode 100644 python/educational-python/resources/fileTemplates/internal/tests.py.ft delete mode 100644 python/educational-python/settings.gradle delete mode 100644 python/src/com/jetbrains/python/PyDocumentationManager.java diff --git a/.idea/modules.xml b/.idea/modules.xml index 91246aeeec48..0713b77690ab 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,7 +3,6 @@ - @@ -92,7 +91,6 @@ - diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy index be2b8da99b90..7a57ee84e947 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityRepositoryModules.groovy @@ -155,10 +155,6 @@ class CommunityRepositoryModules { excludeFromModule("xslt-debugger-engine-impl", "serializer.jar") excludeFromModule("xslt-debugger-engine-impl", "xalan.jar") }, - plugin("Edu-Python") { - withResource("resources/courses", "lib/courses") - excludeFromModule("Edu-Python", "courses/**") - }, plugin("maven") { withModule("maven-jps-plugin") withModule("maven-server-api") diff --git a/python/educational-core/build.gradle b/python/educational-core/build.gradle deleted file mode 100644 index e8c58a5272f6..000000000000 --- a/python/educational-core/build.gradle +++ /dev/null @@ -1,74 +0,0 @@ -import org.apache.tools.ant.filters.ReplaceTokens -buildscript { - ext.kotlin_version = '1.0.6' - repositories { - mavenCentral() - maven { - url "https://oss.sonatype.org/content/repositories/snapshots/" - } - maven { - url 'http://dl.bintray.com/jetbrains/intellij-plugin-service' - } - maven { - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.intellij.plugins:gradle-intellij-plugin:0.2.0-SNAPSHOT" - } -} - - - -apply plugin: 'java' -apply plugin: 'kotlin' -apply plugin: "org.jetbrains.intellij" - -tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } - -sourceSets { - main { - java.srcDir 'src' - kotlin.srcDirs 'src' - resources.srcDir 'resources' - } - test { - java.srcDir 'testSrc' - } -} - -buildDir = new File(rootProject.projectDir, "gradleBuild/" + project.name) - -intellij { - pluginName 'educational-core' - if (project.hasProperty('ideaPath')) { - localPath ideaPath - } else { - if (project.hasProperty('ideaVersion')) { - version ideaVersion - } - } - updateSinceUntilBuild Boolean.valueOf(updateBuildNumber) - downloadSources Boolean.valueOf(downloadIdeaSources) - sandboxDirectory = new File(rootProject.projectDir, "gradleBuild/idea-sandbox") -} - -dependencies { - compile fileTree(dir: 'lib', include: ['*.jar']) -} - -processResources { - filesNotMatching("**/*.png") { - it.filter(ReplaceTokens, tokens: [ - 'VERSION' : version.toString(), - 'BUILD-NUMBER': buildNumber, - ]) - } -} - -afterEvaluate { - tasks.getByName('buildPlugin') { - archiveName = "$intellij.pluginName-$version-${buildNumber}.zip" - } -} \ No newline at end of file diff --git a/python/educational-core/educational-core.iml b/python/educational-core/educational-core.iml deleted file mode 100644 index 811dc90aff11..000000000000 --- a/python/educational-core/educational-core.iml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/python/educational-core/gradle.properties b/python/educational-core/gradle.properties deleted file mode 100644 index 51ed727c2c73..000000000000 --- a/python/educational-core/gradle.properties +++ /dev/null @@ -1,6 +0,0 @@ -ideaVersion IC-LATEST-TRUNK-SNAPSHOT -downloadIdeaSources false -version 4.0 -buildNumber 171 -javaVersion 1.8 -updateBuildNumber false \ No newline at end of file diff --git a/python/educational-core/gradle/wrapper/gradle-wrapper.jar b/python/educational-core/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 941144813d241db74e1bf25b6804c679fbe7f0a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53636 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 DXC5Z7 diff --git a/python/educational-core/gradle/wrapper/gradle-wrapper.properties b/python/educational-core/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 0f29a88b187b..000000000000 --- a/python/educational-core/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Apr 29 19:52:53 MSK 2016 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-bin.zip diff --git a/python/educational-core/gradlew b/python/educational-core/gradlew deleted file mode 100755 index 9d82f7891513..000000000000 --- a/python/educational-core/gradlew +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/python/educational-core/gradlew.bat b/python/educational-core/gradlew.bat deleted file mode 100644 index 8a0b282aa688..000000000000 --- a/python/educational-core/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/python/educational-core/lib/twitter4j-core-4.0.4.jar b/python/educational-core/lib/twitter4j-core-4.0.4.jar deleted file mode 100644 index 029abce2bc28cc3ac6291d9a350398830610465d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 290456 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 z�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 diff --git a/python/educational-core/resources/META-INF/android-dependencies.xml b/python/educational-core/resources/META-INF/android-dependencies.xml deleted file mode 100644 index 6f04cfb24556..000000000000 --- a/python/educational-core/resources/META-INF/android-dependencies.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/python/educational-core/resources/META-INF/edu-intellij.xml b/python/educational-core/resources/META-INF/edu-intellij.xml deleted file mode 100644 index 430c920e2635..000000000000 --- a/python/educational-core/resources/META-INF/edu-intellij.xml +++ /dev/null @@ -1,17 +0,0 @@ - - JUnit - - - - - - - - - - - com.jetbrains.edu.learning.intellij.EduIntellijApplicationComponent - - - - diff --git a/python/educational-core/resources/META-INF/plugin.xml b/python/educational-core/resources/META-INF/plugin.xml deleted file mode 100644 index eb7b15d63514..000000000000 --- a/python/educational-core/resources/META-INF/plugin.xml +++ /dev/null @@ -1,209 +0,0 @@ - - - com.jetbrains.edu.interactivelearning - Educational plugin core - 4.1-IJ2017.3-1 - - - JetBrains - - - - - - - - - com.intellij.modules.lang - com.intellij.modules.androidstudio - com.intellij.modules.java - - - - com.jetbrains.edu.learning.StudyProjectComponent - com.jetbrains.edu.learning.StudyProjectComponent - - - com.jetbrains.edu.coursecreator.CCProjectComponent - - - - - - com.jetbrains.edu.learning.stepic.EduStepicUpdater - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - com.jetbrains.edu.learning.stepic.EduCourseUpdater - - - \ No newline at end of file diff --git a/python/educational-core/resources/code-mirror/codemirror-darcula.css b/python/educational-core/resources/code-mirror/codemirror-darcula.css deleted file mode 100644 index d0eca8c883d8..000000000000 --- a/python/educational-core/resources/code-mirror/codemirror-darcula.css +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright 2000-2014 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. - */ - -body { - margin-left: 20px; - font-family: Helvetica, sans-serif; -} - -code { - font-family: "Source Code Pro", monospace; -} - -/* BASICS */ -.task-help pre code { - background-color: #f7f7f7; - white-space: pre-wrap; - margin-bottom: 10px; - padding: 5px 25px 5px 15px; - display: inline-block; - margin-right: 21px; -} - -.CodeMirror { - /* Set height, width, borders, and global font properties here */ - font-family: monospace; - height: 300px; - color: black; -} - -/* PADDING */ - -.CodeMirror-lines { - padding: 4px 0; /* Vertical padding around content */ -} -.CodeMirror pre { - padding: 0 4px; /* Horizontal padding of content */ -} - -.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - background-color: white; /* The little square between H and V scrollbars */ -} - -/* GUTTER */ - -.CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; - white-space: nowrap; -} -.CodeMirror-linenumbers {} -.CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: #999; - white-space: nowrap; -} - -.CodeMirror-guttermarker { color: black; } -.CodeMirror-guttermarker-subtle { color: #999; } - -/* CURSOR */ - -.CodeMirror-cursor { - border-left: 1px solid black; - border-right: none; - width: 0; -} -/* Shown when moving in bi-directional text */ -.CodeMirror div.CodeMirror-secondarycursor { - border-left: 1px solid silver; -} -.cm-fat-cursor .CodeMirror-cursor { - width: auto; - border: 0; - background: #7e7; -} -.cm-fat-cursor div.CodeMirror-cursors { - z-index: 1; -} - -.cm-animate-fat-cursor { - width: auto; - border: 0; - -webkit-animation: blink 1.06s steps(1) infinite; - -moz-animation: blink 1.06s steps(1) infinite; - animation: blink 1.06s steps(1) infinite; - background-color: #7e7; -} -@-moz-keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} -} -@-webkit-keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} -} -@keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} -} - -/* Can style cursor different in overwrite (non-insert) mode */ -.CodeMirror-overwrite .CodeMirror-cursor {} - -.cm-tab { display: inline-block; text-decoration: inherit; } - -.CodeMirror-ruler { - border-left: 1px solid #ccc; - position: absolute; -} - -/* DEFAULT THEME */ - -.cm-s-default .cm-header {color: #589df6;} -.cm-s-default .cm-quote {color: #6a864c;} -.cm-negative {color: #d44;} -.cm-positive {color: #6a864c;} -.cm-header, .cm-strong {font-weight: bold;} -.cm-em {font-style: italic;} -.cm-link {text-decoration: underline;} -.cm-strikethrough {text-decoration: line-through;} - -.cm-s-default .cm-keyword {color: #774688;} -.cm-s-default .cm-atom {color: #5fa2f6;} -.cm-s-default .cm-number {color: #164;} -.cm-s-default .cm-def {color: #589df6;} -.cm-s-default .cm-variable, -.cm-s-default .cm-punctuation, -.cm-s-default .cm-property, -.cm-s-default .cm-operator {} -.cm-s-default .cm-variable-2 {color: #4c73aa;} -.cm-s-default .cm-variable-3 {color: #085;} -.cm-s-default .cm-comment {color: #aa7e3e;} -.cm-s-default .cm-string {color: #aa3e3e;} -.cm-s-default .cm-string-2 {color: #ff9e39;} -.cm-s-default .cm-meta {color: #555;} -.cm-s-default .cm-qualifier {color: #555;} -.cm-s-default .cm-builtin {color: #6a5caa;} -.cm-s-default .cm-bracket {color: #997;} -.cm-s-default .cm-tag {color: #467734;} -.cm-s-default .cm-attribute {color: #589df6;} -.cm-s-default .cm-hr {color: #999;} -.cm-s-default .cm-link {color: #589df6;} - -.cm-s-default .cm-error {color: #ff4641;} -.cm-invalidchar {color: #ff4641;} - -.CodeMirror-composing { border-bottom: 2px solid; } - -/* Default styles for common addons */ - -div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} -div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} -.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } -.CodeMirror-activeline-background {background: #e8f2ff;} - -/* STOP */ - -/* The rest of this file contains styles related to the mechanics of - the editor. You probably shouldn't touch them. */ - -.CodeMirror { - position: relative; - overflow: hidden; - background: white; -} - -.CodeMirror-scroll { - overflow: scroll !important; /* Things will break if this is overridden */ - /* 30px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror */ - margin-bottom: -30px; margin-right: -30px; - padding-bottom: 30px; - height: 100%; - outline: none; /* Prevent dragging from highlighting the element */ - position: relative; -} -.CodeMirror-sizer { - position: relative; - border-right: 30px solid transparent; -} - -/* The fake, visible scrollbars. Used to force redraw during scrolling - before actuall scrolling happens, thus preventing shaking and - flickering artifacts. */ -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - position: absolute; - z-index: 6; - display: none; -} -.CodeMirror-vscrollbar { - right: 0; top: 0; - overflow-x: hidden; - overflow-y: scroll; -} -.CodeMirror-hscrollbar { - bottom: 0; left: 0; - overflow-y: hidden; - overflow-x: scroll; -} -.CodeMirror-scrollbar-filler { - right: 0; bottom: 0; -} -.CodeMirror-gutter-filler { - left: 0; bottom: 0; -} - -.CodeMirror-gutters { - position: absolute; left: 0; top: 0; - z-index: 3; -} -.CodeMirror-gutter { - white-space: normal; - height: 100%; - display: inline-block; - margin-bottom: -30px; - /* Hack to make IE7 behave */ - *zoom:1; - *display:inline; -} -.CodeMirror-gutter-wrapper { - position: absolute; - z-index: 4; - background: none !important; - border: none !important; -} -.CodeMirror-gutter-background { - position: absolute; - top: 0; bottom: 0; - z-index: 4; -} -.CodeMirror-gutter-elt { - position: absolute; - cursor: default; - z-index: 4; -} -.CodeMirror-gutter-wrapper { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} - -.CodeMirror-lines { - cursor: text; - min-height: 1px; /* prevents collapsing before first draw */ -} -.CodeMirror pre { - /* Reset some styles that the rest of the page might have set */ - -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; - border-width: 0; - background: transparent; - font-family: inherit; - font-size: inherit; - margin: 0; - white-space: pre; - word-wrap: normal; - line-height: inherit; - color: inherit; - z-index: 2; - position: relative; - overflow: visible; - -webkit-tap-highlight-color: transparent; -} -.CodeMirror-wrap pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; -} - -.CodeMirror-linebackground { - position: absolute; - left: 0; right: 0; top: 0; bottom: 0; - z-index: 0; -} - -.CodeMirror-linewidget { - position: relative; - z-index: 2; - overflow: auto; -} - -.CodeMirror-widget {} - -.CodeMirror-code { - outline: none; -} - -/* Force content-box sizing for the elements where we expect it */ -.CodeMirror-scroll, -.CodeMirror-sizer, -.CodeMirror-gutter, -.CodeMirror-gutters, -.CodeMirror-linenumber { - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -.CodeMirror-measure { - position: absolute; - width: 100%; - height: 0; - overflow: hidden; - visibility: hidden; -} - -.CodeMirror-cursor { position: absolute; } -.CodeMirror-measure pre { position: static; } - -div.CodeMirror-cursors { - visibility: hidden; - position: relative; - z-index: 3; -} -div.CodeMirror-dragcursors { - visibility: visible; -} - -.CodeMirror-focused div.CodeMirror-cursors { - visibility: visible; -} - -.CodeMirror-selected { background: #d9d9d9; } -.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } -.CodeMirror-crosshair { cursor: crosshair; } -.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } -.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } - -.cm-searching { - background: #ffa; - background: rgba(255, 255, 0, .4); -} - -/* IE7 hack to prevent it from returning funny offsetTops on the spans */ -.CodeMirror span { *vertical-align: text-bottom; } - -/* Used to force a border model for a node */ -.cm-force-border { padding-right: .1px; } - -@media print { - /* Hide the cursor when printing */ - .CodeMirror div.CodeMirror-cursors { - visibility: hidden; - } -} - -/* See issue #2901 */ -.cm-tab-wrap-hack:after { content: ''; } - -/* Help users use markselection to safely style text background */ -span.CodeMirror-selectedtext { background: none; } \ No newline at end of file diff --git a/python/educational-core/resources/code-mirror/codemirror-old-darcula.css b/python/educational-core/resources/code-mirror/codemirror-old-darcula.css deleted file mode 100644 index 7b26cd5efced..000000000000 --- a/python/educational-core/resources/code-mirror/codemirror-old-darcula.css +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2000-2014 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. - */ - -.cm-s-default span.cm-keyword { - color: #b07732; - font-weight: bold; -} - -.cm-s-default span.cm-softKeyword { - color: #b07732; - font-weight: bold; -} - -.cm-s-default span.cm-atom { - color: #b07732; - font-weight: bold; -} - -.cm-s-default span.cm-number { - color: #b07732; -} - -.cm-s-default span.cm-string { - color: #588652; - font-weight: bold; -} - -.cm-s-default span.cm-def { - color: #dddddd; -} - -.cm-s-default span.cm-variable { - color: #dddddd; -} - -.cm-s-default span.cm-variable-2 { - color: #b07732; -} - -.cm-s-default span.cm-variable-3 { - color: #dddddd; -} - -.cm-s-default span.cm-property { - color: #dddddd; -} - -.cm-s-default span.cm-operator { - color: #dddddd; -} - -.cm-s-default span.cm-comment { - color: #7f7f7f; -} - -.cm-s-default span.cm-string-2 { - color: #ff9336; -} - -.cm-s-default span.cm-meta { - color: #ba9d2a; -} - -.cm-s-default span.cm-error { - color: #944b5e; -} - -.cm-s-default span.cm-qualifier { - color: #555; -} - -.cm-s-default span.cm-builtin { - color: #795faa; -} - -.cm-s-default span.cm-bracket { - color: #cc7; -} - -.cm-s-default span.cm-tag { - color: #ba9d2a; -} - -.cm-s-default span.cm-attribute { - color: #589df6; -} - -.cm-s-default span.cm-header { - color: #9d6baa; -} - -.cm-s-default span.cm-quote { - color: #6a7846; -} - -.cm-s-default span.cm-hr { - color: #999; -} - -.cm-s-default span.cm-link { - color: #589df6; -} - -span.cm-header, span.cm-strong { - font-weight: bold; -} - -span.cm-em { - font-style: italic; -} - -span.cm-emstrong { - font-style: italic; - font-weight: bold; -} - -span.cm-link { - text-decoration: underline; -} - -div.CodeMirror span.CodeMirror-matchingbracket { - /*color: #0000ff;*/ - /*border: 1px #0000ff solid;*/ - /*background-color: #FFE87C;*/ - background-color: #e4e4ff; -} - -div.CodeMirror span.CodeMirror-nonmatchingbracket { - color: #b76b5e; -} - -span.bullet { - padding-right: 5px; -} \ No newline at end of file diff --git a/python/educational-core/resources/code-mirror/codemirror-old.css b/python/educational-core/resources/code-mirror/codemirror-old.css deleted file mode 100644 index ed2ca977e12b..000000000000 --- a/python/educational-core/resources/code-mirror/codemirror-old.css +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2000-2014 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. - */ - -.cm-s-default span.cm-keyword { - color: #000080; - font-weight: bold; -} - -.cm-s-default span.cm-softKeyword { - color: #336699; - font-weight: bold; -} - -.cm-s-default span.cm-atom { - color: #336699; - font-weight: bold; -} - -.cm-s-default span.cm-number { - color: #0000ff; -} - -.cm-s-default span.cm-string { - color: #008000; - font-weight: bold; -} - -.cm-s-default span.cm-def { - color: black; -} - -.cm-s-default span.cm-variable { - color: black; -} - -.cm-s-default span.cm-variable-2 { - color: #05a; -} - -.cm-s-default span.cm-variable-3 { - color: black; -} - -.cm-s-default span.cm-property { - color: black; -} - -.cm-s-default span.cm-operator { - color: black; -} - -.cm-s-default span.cm-comment { - color: #a50; -} - -.cm-s-default span.cm-string-2 { - color: #f50; -} - -.cm-s-default span.cm-meta { - color: #008000; -} - -.cm-s-default span.cm-error { - color: #f00; -} - -.cm-s-default span.cm-qualifier { - color: #555; -} - -.cm-s-default span.cm-builtin { - color: #30a; -} - -.cm-s-default span.cm-bracket { - color: #cc7; -} - -.cm-s-default span.cm-tag { - color: #170; -} - -.cm-s-default span.cm-attribute { - color: #00c; -} - -.cm-s-default span.cm-header { - color: #a0a; -} - -.cm-s-default span.cm-quote { - color: #090; -} - -.cm-s-default span.cm-hr { - color: #999; -} - -.cm-s-default span.cm-link { - color: #00c; -} - -span.cm-header, span.cm-strong { - font-weight: bold; -} - -span.cm-em { - font-style: italic; -} - -span.cm-emstrong { - font-style: italic; - font-weight: bold; -} - -span.cm-link { - text-decoration: underline; -} - -div.CodeMirror span.CodeMirror-matchingbracket { - /*color: #0000ff;*/ - /*border: 1px #0000ff solid;*/ - /*background-color: #FFE87C;*/ - background-color: #e4e4ff; -} - -div.CodeMirror span.CodeMirror-nonmatchingbracket { - color: #f22; -} - -span.bullet { - padding-right: 5px; -} \ No newline at end of file diff --git a/python/educational-core/resources/code-mirror/codemirror.css b/python/educational-core/resources/code-mirror/codemirror.css deleted file mode 100644 index c54eb33f7efa..000000000000 --- a/python/educational-core/resources/code-mirror/codemirror.css +++ /dev/null @@ -1,351 +0,0 @@ -body { - margin-left: 20px; - font-family: Helvetica, sans-serif; - color: #4f5254; -} - -code { - font-family: "Source Code Pro", monospace; -} -/* BASICS */ -.task-help pre code { - background-color: #f7f7f7; - white-space: pre-wrap; - margin-bottom: 10px; - padding: 5px 25px 5px 15px; - display: inline-block; - margin-right: 21px; -} - -.CodeMirror { - /* Set height, width, borders, and global font properties here */ - font-family: monospace; - height: 300px; - color: black; -} - -/* PADDING */ - -.CodeMirror-lines { - padding: 4px 0; /* Vertical padding around content */ -} -.CodeMirror pre { - padding: 0 4px; /* Horizontal padding of content */ -} - -.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - background-color: white; /* The little square between H and V scrollbars */ -} - -/* GUTTER */ - -.CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; - white-space: nowrap; -} -.CodeMirror-linenumbers {} -.CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: #999; - white-space: nowrap; -} - -.CodeMirror-guttermarker { color: black; } -.CodeMirror-guttermarker-subtle { color: #999; } - -/* CURSOR */ - -.CodeMirror-cursor { - border-left: 1px solid black; - border-right: none; - width: 0; -} -/* Shown when moving in bi-directional text */ -.CodeMirror div.CodeMirror-secondarycursor { - border-left: 1px solid silver; -} -.cm-fat-cursor .CodeMirror-cursor { - width: auto; - border: 0; - background: #7e7; -} -.cm-fat-cursor div.CodeMirror-cursors { - z-index: 1; -} - -.cm-animate-fat-cursor { - width: auto; - border: 0; - -webkit-animation: blink 1.06s steps(1) infinite; - -moz-animation: blink 1.06s steps(1) infinite; - animation: blink 1.06s steps(1) infinite; - background-color: #7e7; -} -@-moz-keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} -} -@-webkit-keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} -} -@keyframes blink { - 0% {} - 50% { background-color: transparent; } - 100% {} -} - -/* Can style cursor different in overwrite (non-insert) mode */ -.CodeMirror-overwrite .CodeMirror-cursor {} - -.cm-tab { display: inline-block; text-decoration: inherit; } - -.CodeMirror-ruler { - border-left: 1px solid #ccc; - position: absolute; -} - -/* DEFAULT THEME */ - -.cm-s-default .cm-header {color: blue;} -.cm-s-default .cm-quote {color: #090;} -.cm-negative {color: #d44;} -.cm-positive {color: #292;} -.cm-header, .cm-strong {font-weight: bold;} -.cm-em {font-style: italic;} -.cm-link {text-decoration: underline;} -.cm-strikethrough {text-decoration: line-through;} - -.cm-s-default .cm-keyword {color: #708;} -.cm-s-default .cm-atom {color: #219;} -.cm-s-default .cm-number {color: #164;} -.cm-s-default .cm-def {color: #00f;} -.cm-s-default .cm-variable, -.cm-s-default .cm-punctuation, -.cm-s-default .cm-property, -.cm-s-default .cm-operator {} -.cm-s-default .cm-variable-2 {color: #05a;} -.cm-s-default .cm-variable-3 {color: #085;} -.cm-s-default .cm-comment {color: #a50;} -.cm-s-default .cm-string {color: #a11;} -.cm-s-default .cm-string-2 {color: #f50;} -.cm-s-default .cm-meta {color: #555;} -.cm-s-default .cm-qualifier {color: #555;} -.cm-s-default .cm-builtin {color: #30a;} -.cm-s-default .cm-bracket {color: #997;} -.cm-s-default .cm-tag {color: #170;} -.cm-s-default .cm-attribute {color: #00c;} -.cm-s-default .cm-hr {color: #999;} -.cm-s-default .cm-link {color: #00c;} - -.cm-s-default .cm-error {color: #f00;} -.cm-invalidchar {color: #f00;} - -.CodeMirror-composing { border-bottom: 2px solid; } - -/* Default styles for common addons */ - -div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} -div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} -.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } -.CodeMirror-activeline-background {background: #e8f2ff;} - -/* STOP */ - -/* The rest of this file contains styles related to the mechanics of - the editor. You probably shouldn't touch them. */ - -.CodeMirror { - position: relative; - overflow: hidden; - background: white; -} - -.CodeMirror-scroll { - overflow: scroll !important; /* Things will break if this is overridden */ - /* 30px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror */ - margin-bottom: -30px; margin-right: -30px; - padding-bottom: 30px; - height: 100%; - outline: none; /* Prevent dragging from highlighting the element */ - position: relative; -} -.CodeMirror-sizer { - position: relative; - border-right: 30px solid transparent; -} - -/* The fake, visible scrollbars. Used to force redraw during scrolling - before actuall scrolling happens, thus preventing shaking and - flickering artifacts. */ -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - position: absolute; - z-index: 6; - display: none; -} -.CodeMirror-vscrollbar { - right: 0; top: 0; - overflow-x: hidden; - overflow-y: scroll; -} -.CodeMirror-hscrollbar { - bottom: 0; left: 0; - overflow-y: hidden; - overflow-x: scroll; -} -.CodeMirror-scrollbar-filler { - right: 0; bottom: 0; -} -.CodeMirror-gutter-filler { - left: 0; bottom: 0; -} - -.CodeMirror-gutters { - position: absolute; left: 0; top: 0; - z-index: 3; -} -.CodeMirror-gutter { - white-space: normal; - height: 100%; - display: inline-block; - margin-bottom: -30px; - /* Hack to make IE7 behave */ - *zoom:1; - *display:inline; -} -.CodeMirror-gutter-wrapper { - position: absolute; - z-index: 4; - background: none !important; - border: none !important; -} -.CodeMirror-gutter-background { - position: absolute; - top: 0; bottom: 0; - z-index: 4; -} -.CodeMirror-gutter-elt { - position: absolute; - cursor: default; - z-index: 4; -} -.CodeMirror-gutter-wrapper { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} - -.CodeMirror-lines { - cursor: text; - min-height: 1px; /* prevents collapsing before first draw */ -} -.CodeMirror pre { - /* Reset some styles that the rest of the page might have set */ - -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; - border-width: 0; - background: transparent; - font-family: inherit; - font-size: inherit; - margin: 0; - white-space: pre; - word-wrap: normal; - line-height: inherit; - color: inherit; - z-index: 2; - position: relative; - overflow: visible; - -webkit-tap-highlight-color: transparent; -} -.CodeMirror-wrap pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; -} - -.CodeMirror-linebackground { - position: absolute; - left: 0; right: 0; top: 0; bottom: 0; - z-index: 0; -} - -.CodeMirror-linewidget { - position: relative; - z-index: 2; - overflow: auto; -} - -.CodeMirror-widget {} - -.CodeMirror-code { - outline: none; -} - -/* Force content-box sizing for the elements where we expect it */ -.CodeMirror-scroll, -.CodeMirror-sizer, -.CodeMirror-gutter, -.CodeMirror-gutters, -.CodeMirror-linenumber { - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -.CodeMirror-measure { - position: absolute; - width: 100%; - height: 0; - overflow: hidden; - visibility: hidden; -} - -.CodeMirror-cursor { position: absolute; } -.CodeMirror-measure pre { position: static; } - -div.CodeMirror-cursors { - visibility: hidden; - position: relative; - z-index: 3; -} -div.CodeMirror-dragcursors { - visibility: visible; -} - -.CodeMirror-focused div.CodeMirror-cursors { - visibility: visible; -} - -.CodeMirror-selected { background: #d9d9d9; } -.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } -.CodeMirror-crosshair { cursor: crosshair; } -.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } -.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } - -.cm-searching { - background: #ffa; - background: rgba(255, 255, 0, .4); -} - -/* IE7 hack to prevent it from returning funny offsetTops on the spans */ -.CodeMirror span { *vertical-align: text-bottom; } - -/* Used to force a border model for a node */ -.cm-force-border { padding-right: .1px; } - -@media print { - /* Hide the cursor when printing */ - .CodeMirror div.CodeMirror-cursors { - visibility: hidden; - } -} - -/* See issue #2901 */ -.cm-tab-wrap-hack:after { content: ''; } - -/* Help users use markselection to safely style text background */ -span.CodeMirror-selectedtext { background: none; } \ No newline at end of file diff --git a/python/educational-core/resources/code-mirror/codemirror.js b/python/educational-core/resources/code-mirror/codemirror.js deleted file mode 100644 index 48f71cdb7811..000000000000 --- a/python/educational-core/resources/code-mirror/codemirror.js +++ /dev/null @@ -1,8892 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -// This is CodeMirror (http://codemirror.net), a code editor -// implemented in JavaScript on top of the browser's DOM. -// -// You can find some technical background for some of the code below -// at http://marijnhaverbeke.nl/blog/#cm-internals . - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - module.exports = mod(); - else if (typeof define == "function" && define.amd) // AMD - return define([], mod); - else // Plain browser env - (this || window).CodeMirror = mod(); -})(function() { - "use strict"; - - // BROWSER SNIFFING - - // Kludges for bugs and behavior differences that can't be feature - // detected are enabled based on userAgent etc sniffing. - var userAgent = navigator.userAgent; - var platform = navigator.platform; - - var gecko = /gecko\/\d/i.test(userAgent); - var ie_upto10 = /MSIE \d/.test(userAgent); - var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent); - var ie = ie_upto10 || ie_11up; - var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); - var webkit = /WebKit\//.test(userAgent); - var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent); - var chrome = /Chrome\//.test(userAgent); - var presto = /Opera\//.test(userAgent); - var safari = /Apple Computer/.test(navigator.vendor); - var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent); - var phantom = /PhantomJS/.test(userAgent); - - var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent); - // This is woefully incomplete. Suggestions for alternative methods welcome. - var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent); - var mac = ios || /Mac/.test(platform); - var windows = /win/i.test(platform); - - var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/); - if (presto_version) presto_version = Number(presto_version[1]); - if (presto_version && presto_version >= 15) { presto = false; webkit = true; } - // Some browsers use the wrong event properties to signal cmd/ctrl on OS X - var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); - var captureRightClick = gecko || (ie && ie_version >= 9); - - // Optimize some code when these features are not used. - var sawReadOnlySpans = false, sawCollapsedSpans = false; - - // EDITOR CONSTRUCTOR - - // A CodeMirror instance represents an editor. This is the object - // that user code is usually dealing with. - - function CodeMirror(place, options) { - if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); - - this.options = options = options ? copyObj(options) : {}; - // Determine effective options based on given values and defaults. - copyObj(defaults, options, false); - setGuttersForLineNumbers(options); - - var doc = options.value; - if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator); - this.doc = doc; - - var input = new CodeMirror.inputStyles[options.inputStyle](this); - var display = this.display = new Display(place, doc, input); - display.wrapper.CodeMirror = this; - updateGutters(this); - themeChanged(this); - if (options.lineWrapping) - this.display.wrapper.className += " CodeMirror-wrap"; - if (options.autofocus && !mobile) display.input.focus(); - initScrollbars(this); - - this.state = { - keyMaps: [], // stores maps added by addKeyMap - overlays: [], // highlighting overlays, as added by addOverlay - modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info - overwrite: false, - delayingBlurEvent: false, - focused: false, - suppressEdits: false, // used to disable editing during key handlers when in readOnly mode - pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll - selectingText: false, - draggingText: false, - highlight: new Delayed(), // stores highlight worker timeout - keySeq: null, // Unfinished key sequence - specialChars: null - }; - - var cm = this; - - // Override magic textarea content restore that IE sometimes does - // on our hidden textarea on reload - if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20); - - registerEventHandlers(this); - ensureGlobalHandlers(); - - startOperation(this); - this.curOp.forceUpdate = true; - attachDoc(this, doc); - - if ((options.autofocus && !mobile) || cm.hasFocus()) - setTimeout(bind(onFocus, this), 20); - else - onBlur(this); - - for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) - optionHandlers[opt](this, options[opt], Init); - maybeUpdateLineNumberWidth(this); - if (options.finishInit) options.finishInit(this); - for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); - endOperation(this); - // Suppress optimizelegibility in Webkit, since it breaks text - // measuring on line wrapping boundaries. - if (webkit && options.lineWrapping && - getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") - display.lineDiv.style.textRendering = "auto"; - } - - // DISPLAY CONSTRUCTOR - - // The display handles the DOM integration, both for input reading - // and content drawing. It holds references to DOM nodes and - // display-related state. - - function Display(place, doc, input) { - var d = this; - this.input = input; - - // Covers bottom-right square when both scrollbars are present. - d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); - d.scrollbarFiller.setAttribute("cm-not-content", "true"); - // Covers bottom of gutter when coverGutterNextToScrollbar is on - // and h scrollbar is present. - d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); - d.gutterFiller.setAttribute("cm-not-content", "true"); - // Will contain the actual code, positioned to cover the viewport. - d.lineDiv = elt("div", null, "CodeMirror-code"); - // Elements are added to these to represent selection and cursors. - d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); - d.cursorDiv = elt("div", null, "CodeMirror-cursors"); - // A visibility: hidden element used to find the size of things. - d.measure = elt("div", null, "CodeMirror-measure"); - // When lines outside of the viewport are measured, they are drawn in this. - d.lineMeasure = elt("div", null, "CodeMirror-measure"); - // Wraps everything that needs to exist inside the vertically-padded coordinate system - d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], - null, "position: relative; outline: none"); - // Moved around its parent to cover visible view. - d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); - // Set to the height of the document, allowing scrolling. - d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); - d.sizerWidth = null; - // Behavior of elts with overflow: auto and padding is - // inconsistent across browsers. This is used to ensure the - // scrollable area is big enough. - d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); - // Will contain the gutters, if any. - d.gutters = elt("div", null, "CodeMirror-gutters"); - d.lineGutter = null; - // Actual scrollable element. - d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); - d.scroller.setAttribute("tabIndex", "-1"); - // The element in which the editor lives. - d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); - - // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) - if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } - if (!webkit && !(gecko && mobile)) d.scroller.draggable = true; - - if (place) { - if (place.appendChild) place.appendChild(d.wrapper); - else place(d.wrapper); - } - - // Current rendered range (may be bigger than the view window). - d.viewFrom = d.viewTo = doc.first; - d.reportedViewFrom = d.reportedViewTo = doc.first; - // Information about the rendered lines. - d.view = []; - d.renderedView = null; - // Holds info about a single rendered line when it was rendered - // for measurement, while not in view. - d.externalMeasured = null; - // Empty space (in pixels) above the view - d.viewOffset = 0; - d.lastWrapHeight = d.lastWrapWidth = 0; - d.updateLineNumbers = null; - - d.nativeBarWidth = d.barHeight = d.barWidth = 0; - d.scrollbarsClipped = false; - - // Used to only resize the line number gutter when necessary (when - // the amount of lines crosses a boundary that makes its width change) - d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; - // Set to true when a non-horizontal-scrolling line widget is - // added. As an optimization, line widget aligning is skipped when - // this is false. - d.alignWidgets = false; - - d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; - - // Tracks the maximum line length so that the horizontal scrollbar - // can be kept static when scrolling. - d.maxLine = null; - d.maxLineLength = 0; - d.maxLineChanged = false; - - // Used for measuring wheel scrolling granularity - d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; - - // True when shift is held down. - d.shift = false; - - // Used to track whether anything happened since the context menu - // was opened. - d.selForContextMenu = null; - - d.activeTouch = null; - - input.init(d); - } - - // STATE UPDATES - - // Used to get the editor into a consistent state again when options change. - - function loadMode(cm) { - cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); - resetModeState(cm); - } - - function resetModeState(cm) { - cm.doc.iter(function(line) { - if (line.stateAfter) line.stateAfter = null; - if (line.styles) line.styles = null; - }); - cm.doc.frontier = cm.doc.first; - startWorker(cm, 100); - cm.state.modeGen++; - if (cm.curOp) regChange(cm); - } - - function wrappingChanged(cm) { - if (cm.options.lineWrapping) { - addClass(cm.display.wrapper, "CodeMirror-wrap"); - cm.display.sizer.style.minWidth = ""; - cm.display.sizerWidth = null; - } else { - rmClass(cm.display.wrapper, "CodeMirror-wrap"); - findMaxLine(cm); - } - estimateLineHeights(cm); - regChange(cm); - clearCaches(cm); - setTimeout(function(){updateScrollbars(cm);}, 100); - } - - // Returns a function that estimates the height of a line, to use as - // first approximation until the line becomes visible (and is thus - // properly measurable). - function estimateHeight(cm) { - var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; - var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); - return function(line) { - if (lineIsHidden(cm.doc, line)) return 0; - - var widgetsHeight = 0; - if (line.widgets) for (var i = 0; i < line.widgets.length; i++) { - if (line.widgets[i].height) widgetsHeight += line.widgets[i].height; - } - - if (wrapping) - return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; - else - return widgetsHeight + th; - }; - } - - function estimateLineHeights(cm) { - var doc = cm.doc, est = estimateHeight(cm); - doc.iter(function(line) { - var estHeight = est(line); - if (estHeight != line.height) updateLineHeight(line, estHeight); - }); - } - - function themeChanged(cm) { - cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + - cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); - clearCaches(cm); - } - - function guttersChanged(cm) { - updateGutters(cm); - regChange(cm); - setTimeout(function(){alignHorizontally(cm);}, 20); - } - - // Rebuild the gutter elements, ensure the margin to the left of the - // code matches their width. - function updateGutters(cm) { - var gutters = cm.display.gutters, specs = cm.options.gutters; - removeChildren(gutters); - for (var i = 0; i < specs.length; ++i) { - var gutterClass = specs[i]; - var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); - if (gutterClass == "CodeMirror-linenumbers") { - cm.display.lineGutter = gElt; - gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; - } - } - gutters.style.display = i ? "" : "none"; - updateGutterSpace(cm); - } - - function updateGutterSpace(cm) { - var width = cm.display.gutters.offsetWidth; - cm.display.sizer.style.marginLeft = width + "px"; - } - - // Compute the character length of a line, taking into account - // collapsed ranges (see markText) that might hide parts, and join - // other lines onto it. - function lineLength(line) { - if (line.height == 0) return 0; - var len = line.text.length, merged, cur = line; - while (merged = collapsedSpanAtStart(cur)) { - var found = merged.find(0, true); - cur = found.from.line; - len += found.from.ch - found.to.ch; - } - cur = line; - while (merged = collapsedSpanAtEnd(cur)) { - var found = merged.find(0, true); - len -= cur.text.length - found.from.ch; - cur = found.to.line; - len += cur.text.length - found.to.ch; - } - return len; - } - - // Find the longest line in the document. - function findMaxLine(cm) { - var d = cm.display, doc = cm.doc; - d.maxLine = getLine(doc, doc.first); - d.maxLineLength = lineLength(d.maxLine); - d.maxLineChanged = true; - doc.iter(function(line) { - var len = lineLength(line); - if (len > d.maxLineLength) { - d.maxLineLength = len; - d.maxLine = line; - } - }); - } - - // Make sure the gutters options contains the element - // "CodeMirror-linenumbers" when the lineNumbers option is true. - function setGuttersForLineNumbers(options) { - var found = indexOf(options.gutters, "CodeMirror-linenumbers"); - if (found == -1 && options.lineNumbers) { - options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); - } else if (found > -1 && !options.lineNumbers) { - options.gutters = options.gutters.slice(0); - options.gutters.splice(found, 1); - } - } - - // SCROLLBARS - - // Prepare DOM reads needed to update the scrollbars. Done in one - // shot to minimize update/measure roundtrips. - function measureForScrollbars(cm) { - var d = cm.display, gutterW = d.gutters.offsetWidth; - var docH = Math.round(cm.doc.height + paddingVert(cm.display)); - return { - clientHeight: d.scroller.clientHeight, - viewHeight: d.wrapper.clientHeight, - scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, - viewWidth: d.wrapper.clientWidth, - barLeft: cm.options.fixedGutter ? gutterW : 0, - docHeight: docH, - scrollHeight: docH + scrollGap(cm) + d.barHeight, - nativeBarWidth: d.nativeBarWidth, - gutterWidth: gutterW - }; - } - - function NativeScrollbars(place, scroll, cm) { - this.cm = cm; - var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); - var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); - place(vert); place(horiz); - - on(vert, "scroll", function() { - if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); - }); - on(horiz, "scroll", function() { - if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); - }); - - this.checkedZeroWidth = false; - // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). - if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; - } - - NativeScrollbars.prototype = copyObj({ - update: function(measure) { - var needsH = measure.scrollWidth > measure.clientWidth + 1; - var needsV = measure.scrollHeight > measure.clientHeight + 1; - var sWidth = measure.nativeBarWidth; - - if (needsV) { - this.vert.style.display = "block"; - this.vert.style.bottom = needsH ? sWidth + "px" : "0"; - var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); - // A bug in IE8 can cause this value to be negative, so guard it. - this.vert.firstChild.style.height = - Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; - } else { - this.vert.style.display = ""; - this.vert.firstChild.style.height = "0"; - } - - if (needsH) { - this.horiz.style.display = "block"; - this.horiz.style.right = needsV ? sWidth + "px" : "0"; - this.horiz.style.left = measure.barLeft + "px"; - var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); - this.horiz.firstChild.style.width = - (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; - } else { - this.horiz.style.display = ""; - this.horiz.firstChild.style.width = "0"; - } - - if (!this.checkedZeroWidth && measure.clientHeight > 0) { - if (sWidth == 0) this.zeroWidthHack(); - this.checkedZeroWidth = true; - } - - return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; - }, - setScrollLeft: function(pos) { - if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; - if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz); - }, - setScrollTop: function(pos) { - if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; - if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert); - }, - zeroWidthHack: function() { - var w = mac && !mac_geMountainLion ? "12px" : "18px"; - this.horiz.style.height = this.vert.style.width = w; - this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none"; - this.disableHoriz = new Delayed; - this.disableVert = new Delayed; - }, - enableZeroWidthBar: function(bar, delay) { - bar.style.pointerEvents = "auto"; - function maybeDisable() { - // To find out whether the scrollbar is still visible, we - // check whether the element under the pixel in the bottom - // left corner of the scrollbar box is the scrollbar box - // itself (when the bar is still visible) or its filler child - // (when the bar is hidden). If it is still visible, we keep - // it enabled, if it's hidden, we disable pointer events. - var box = bar.getBoundingClientRect(); - var elt = document.elementFromPoint(box.left + 1, box.bottom - 1); - if (elt != bar) bar.style.pointerEvents = "none"; - else delay.set(1000, maybeDisable); - } - delay.set(1000, maybeDisable); - }, - clear: function() { - var parent = this.horiz.parentNode; - parent.removeChild(this.horiz); - parent.removeChild(this.vert); - } - }, NativeScrollbars.prototype); - - function NullScrollbars() {} - - NullScrollbars.prototype = copyObj({ - update: function() { return {bottom: 0, right: 0}; }, - setScrollLeft: function() {}, - setScrollTop: function() {}, - clear: function() {} - }, NullScrollbars.prototype); - - CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; - - function initScrollbars(cm) { - if (cm.display.scrollbars) { - cm.display.scrollbars.clear(); - if (cm.display.scrollbars.addClass) - rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); - } - - cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) { - cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); - // Prevent clicks in the scrollbars from killing focus - on(node, "mousedown", function() { - if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0); - }); - node.setAttribute("cm-not-content", "true"); - }, function(pos, axis) { - if (axis == "horizontal") setScrollLeft(cm, pos); - else setScrollTop(cm, pos); - }, cm); - if (cm.display.scrollbars.addClass) - addClass(cm.display.wrapper, cm.display.scrollbars.addClass); - } - - function updateScrollbars(cm, measure) { - if (!measure) measure = measureForScrollbars(cm); - var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; - updateScrollbarsInner(cm, measure); - for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { - if (startWidth != cm.display.barWidth && cm.options.lineWrapping) - updateHeightsInViewport(cm); - updateScrollbarsInner(cm, measureForScrollbars(cm)); - startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; - } - } - - // Re-synchronize the fake scrollbars with the actual size of the - // content. - function updateScrollbarsInner(cm, measure) { - var d = cm.display; - var sizes = d.scrollbars.update(measure); - - d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; - d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; - - if (sizes.right && sizes.bottom) { - d.scrollbarFiller.style.display = "block"; - d.scrollbarFiller.style.height = sizes.bottom + "px"; - d.scrollbarFiller.style.width = sizes.right + "px"; - } else d.scrollbarFiller.style.display = ""; - if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { - d.gutterFiller.style.display = "block"; - d.gutterFiller.style.height = sizes.bottom + "px"; - d.gutterFiller.style.width = measure.gutterWidth + "px"; - } else d.gutterFiller.style.display = ""; - } - - // Compute the lines that are visible in a given viewport (defaults - // the the current scroll position). viewport may contain top, - // height, and ensure (see op.scrollToPos) properties. - function visibleLines(display, doc, viewport) { - var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; - top = Math.floor(top - paddingTop(display)); - var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; - - var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); - // Ensure is a {from: {line, ch}, to: {line, ch}} object, and - // forces those lines into the viewport (if possible). - if (viewport && viewport.ensure) { - var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; - if (ensureFrom < from) { - from = ensureFrom; - to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); - } else if (Math.min(ensureTo, doc.lastLine()) >= to) { - from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); - to = ensureTo; - } - } - return {from: from, to: Math.max(to, from + 1)}; - } - - // LINE NUMBERS - - // Re-align line numbers and gutter marks to compensate for - // horizontal scrolling. - function alignHorizontally(cm) { - var display = cm.display, view = display.view; - if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; - var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; - var gutterW = display.gutters.offsetWidth, left = comp + "px"; - for (var i = 0; i < view.length; i++) if (!view[i].hidden) { - if (cm.options.fixedGutter && view[i].gutter) - view[i].gutter.style.left = left; - var align = view[i].alignable; - if (align) for (var j = 0; j < align.length; j++) - align[j].style.left = left; - } - if (cm.options.fixedGutter) - display.gutters.style.left = (comp + gutterW) + "px"; - } - - // Used to ensure that the line number gutter is still the right - // size for the current document size. Returns true when an update - // is needed. - function maybeUpdateLineNumberWidth(cm) { - if (!cm.options.lineNumbers) return false; - var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; - if (last.length != display.lineNumChars) { - var test = display.measure.appendChild(elt("div", [elt("div", last)], - "CodeMirror-linenumber CodeMirror-gutter-elt")); - var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; - display.lineGutter.style.width = ""; - display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; - display.lineNumWidth = display.lineNumInnerWidth + padding; - display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; - display.lineGutter.style.width = display.lineNumWidth + "px"; - updateGutterSpace(cm); - return true; - } - return false; - } - - function lineNumberFor(options, i) { - return String(options.lineNumberFormatter(i + options.firstLineNumber)); - } - - // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, - // but using getBoundingClientRect to get a sub-pixel-accurate - // result. - function compensateForHScroll(display) { - return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; - } - - // DISPLAY DRAWING - - function DisplayUpdate(cm, viewport, force) { - var display = cm.display; - - this.viewport = viewport; - // Store some values that we'll need later (but don't want to force a relayout for) - this.visible = visibleLines(display, cm.doc, viewport); - this.editorIsHidden = !display.wrapper.offsetWidth; - this.wrapperHeight = display.wrapper.clientHeight; - this.wrapperWidth = display.wrapper.clientWidth; - this.oldDisplayWidth = displayWidth(cm); - this.force = force; - this.dims = getDimensions(cm); - this.events = []; - } - - DisplayUpdate.prototype.signal = function(emitter, type) { - if (hasHandler(emitter, type)) - this.events.push(arguments); - }; - DisplayUpdate.prototype.finish = function() { - for (var i = 0; i < this.events.length; i++) - signal.apply(null, this.events[i]); - }; - - function maybeClipScrollbars(cm) { - var display = cm.display; - if (!display.scrollbarsClipped && display.scroller.offsetWidth) { - display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; - display.heightForcer.style.height = scrollGap(cm) + "px"; - display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; - display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; - display.scrollbarsClipped = true; - } - } - - // Does the actual updating of the line display. Bails out - // (returning false) when there is nothing to be done and forced is - // false. - function updateDisplayIfNeeded(cm, update) { - var display = cm.display, doc = cm.doc; - - if (update.editorIsHidden) { - resetView(cm); - return false; - } - - // Bail out if the visible area is already rendered and nothing changed. - if (!update.force && - update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && - (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && - display.renderedView == display.view && countDirtyView(cm) == 0) - return false; - - if (maybeUpdateLineNumberWidth(cm)) { - resetView(cm); - update.dims = getDimensions(cm); - } - - // Compute a suitable new viewport (from & to) - var end = doc.first + doc.size; - var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); - var to = Math.min(end, update.visible.to + cm.options.viewportMargin); - if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); - if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); - if (sawCollapsedSpans) { - from = visualLineNo(cm.doc, from); - to = visualLineEndNo(cm.doc, to); - } - - var different = from != display.viewFrom || to != display.viewTo || - display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; - adjustView(cm, from, to); - - display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); - // Position the mover div to align with the current scroll position - cm.display.mover.style.top = display.viewOffset + "px"; - - var toUpdate = countDirtyView(cm); - if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && - (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) - return false; - - // For big changes, we hide the enclosing element during the - // update, since that speeds up the operations on most browsers. - var focused = activeElt(); - if (toUpdate > 4) display.lineDiv.style.display = "none"; - patchDisplay(cm, display.updateLineNumbers, update.dims); - if (toUpdate > 4) display.lineDiv.style.display = ""; - display.renderedView = display.view; - // There might have been a widget with a focused element that got - // hidden or updated, if so re-focus it. - if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); - - // Prevent selection and cursors from interfering with the scroll - // width and height. - removeChildren(display.cursorDiv); - removeChildren(display.selectionDiv); - display.gutters.style.height = display.sizer.style.minHeight = 0; - - if (different) { - display.lastWrapHeight = update.wrapperHeight; - display.lastWrapWidth = update.wrapperWidth; - startWorker(cm, 400); - } - - display.updateLineNumbers = null; - - return true; - } - - function postUpdateDisplay(cm, update) { - var viewport = update.viewport; - for (var first = true;; first = false) { - if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { - // Clip forced viewport to actual scrollable area. - if (viewport && viewport.top != null) - viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; - // Updated line heights might result in the drawn area not - // actually covering the viewport. Keep looping until it does. - update.visible = visibleLines(cm.display, cm.doc, viewport); - if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) - break; - } - if (!updateDisplayIfNeeded(cm, update)) break; - updateHeightsInViewport(cm); - var barMeasure = measureForScrollbars(cm); - updateSelection(cm); - setDocumentHeight(cm, barMeasure); - updateScrollbars(cm, barMeasure); - } - - update.signal(cm, "update", cm); - if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { - update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); - cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; - } - } - - function updateDisplaySimple(cm, viewport) { - var update = new DisplayUpdate(cm, viewport); - if (updateDisplayIfNeeded(cm, update)) { - updateHeightsInViewport(cm); - postUpdateDisplay(cm, update); - var barMeasure = measureForScrollbars(cm); - updateSelection(cm); - setDocumentHeight(cm, barMeasure); - updateScrollbars(cm, barMeasure); - update.finish(); - } - } - - function setDocumentHeight(cm, measure) { - cm.display.sizer.style.minHeight = measure.docHeight + "px"; - var total = measure.docHeight + cm.display.barHeight; - cm.display.heightForcer.style.top = total + "px"; - cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "px"; - } - - // Read the actual heights of the rendered lines, and update their - // stored heights to match. - function updateHeightsInViewport(cm) { - var display = cm.display; - var prevBottom = display.lineDiv.offsetTop; - for (var i = 0; i < display.view.length; i++) { - var cur = display.view[i], height; - if (cur.hidden) continue; - if (ie && ie_version < 8) { - var bot = cur.node.offsetTop + cur.node.offsetHeight; - height = bot - prevBottom; - prevBottom = bot; - } else { - var box = cur.node.getBoundingClientRect(); - height = box.bottom - box.top; - } - var diff = cur.line.height - height; - if (height < 2) height = textHeight(display); - if (diff > .001 || diff < -.001) { - updateLineHeight(cur.line, height); - updateWidgetHeight(cur.line); - if (cur.rest) for (var j = 0; j < cur.rest.length; j++) - updateWidgetHeight(cur.rest[j]); - } - } - } - - // Read and store the height of line widgets associated with the - // given line. - function updateWidgetHeight(line) { - if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) - line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight; - } - - // Do a bulk-read of the DOM positions and sizes needed to draw the - // view, so that we don't interleave reading and writing to the DOM. - function getDimensions(cm) { - var d = cm.display, left = {}, width = {}; - var gutterLeft = d.gutters.clientLeft; - for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { - left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; - width[cm.options.gutters[i]] = n.clientWidth; - } - return {fixedPos: compensateForHScroll(d), - gutterTotalWidth: d.gutters.offsetWidth, - gutterLeft: left, - gutterWidth: width, - wrapperWidth: d.wrapper.clientWidth}; - } - - // Sync the actual display DOM structure with display.view, removing - // nodes for lines that are no longer in view, and creating the ones - // that are not there yet, and updating the ones that are out of - // date. - function patchDisplay(cm, updateNumbersFrom, dims) { - var display = cm.display, lineNumbers = cm.options.lineNumbers; - var container = display.lineDiv, cur = container.firstChild; - - function rm(node) { - var next = node.nextSibling; - // Works around a throw-scroll bug in OS X Webkit - if (webkit && mac && cm.display.currentWheelTarget == node) - node.style.display = "none"; - else - node.parentNode.removeChild(node); - return next; - } - - var view = display.view, lineN = display.viewFrom; - // Loop over the elements in the view, syncing cur (the DOM nodes - // in display.lineDiv) with the view as we go. - for (var i = 0; i < view.length; i++) { - var lineView = view[i]; - if (lineView.hidden) { - } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet - var node = buildLineElement(cm, lineView, lineN, dims); - container.insertBefore(node, cur); - } else { // Already drawn - while (cur != lineView.node) cur = rm(cur); - var updateNumber = lineNumbers && updateNumbersFrom != null && - updateNumbersFrom <= lineN && lineView.lineNumber; - if (lineView.changes) { - if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; - updateLineForChanges(cm, lineView, lineN, dims); - } - if (updateNumber) { - removeChildren(lineView.lineNumber); - lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); - } - cur = lineView.node.nextSibling; - } - lineN += lineView.size; - } - while (cur) cur = rm(cur); - } - - // When an aspect of a line changes, a string is added to - // lineView.changes. This updates the relevant part of the line's - // DOM structure. - function updateLineForChanges(cm, lineView, lineN, dims) { - for (var j = 0; j < lineView.changes.length; j++) { - var type = lineView.changes[j]; - if (type == "text") updateLineText(cm, lineView); - else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); - else if (type == "class") updateLineClasses(lineView); - else if (type == "widget") updateLineWidgets(cm, lineView, dims); - } - lineView.changes = null; - } - - // Lines with gutter elements, widgets or a background class need to - // be wrapped, and have the extra elements added to the wrapper div - function ensureLineWrapped(lineView) { - if (lineView.node == lineView.text) { - lineView.node = elt("div", null, null, "position: relative"); - if (lineView.text.parentNode) - lineView.text.parentNode.replaceChild(lineView.node, lineView.text); - lineView.node.appendChild(lineView.text); - if (ie && ie_version < 8) lineView.node.style.zIndex = 2; - } - return lineView.node; - } - - function updateLineBackground(lineView) { - var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; - if (cls) cls += " CodeMirror-linebackground"; - if (lineView.background) { - if (cls) lineView.background.className = cls; - else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } - } else if (cls) { - var wrap = ensureLineWrapped(lineView); - lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); - } - } - - // Wrapper around buildLineContent which will reuse the structure - // in display.externalMeasured when possible. - function getLineContent(cm, lineView) { - var ext = cm.display.externalMeasured; - if (ext && ext.line == lineView.line) { - cm.display.externalMeasured = null; - lineView.measure = ext.measure; - return ext.built; - } - return buildLineContent(cm, lineView); - } - - // Redraw the line's text. Interacts with the background and text - // classes because the mode may output tokens that influence these - // classes. - function updateLineText(cm, lineView) { - var cls = lineView.text.className; - var built = getLineContent(cm, lineView); - if (lineView.text == lineView.node) lineView.node = built.pre; - lineView.text.parentNode.replaceChild(built.pre, lineView.text); - lineView.text = built.pre; - if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { - lineView.bgClass = built.bgClass; - lineView.textClass = built.textClass; - updateLineClasses(lineView); - } else if (cls) { - lineView.text.className = cls; - } - } - - function updateLineClasses(lineView) { - updateLineBackground(lineView); - if (lineView.line.wrapClass) - ensureLineWrapped(lineView).className = lineView.line.wrapClass; - else if (lineView.node != lineView.text) - lineView.node.className = ""; - var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; - lineView.text.className = textClass || ""; - } - - function updateLineGutter(cm, lineView, lineN, dims) { - if (lineView.gutter) { - lineView.node.removeChild(lineView.gutter); - lineView.gutter = null; - } - if (lineView.gutterBackground) { - lineView.node.removeChild(lineView.gutterBackground); - lineView.gutterBackground = null; - } - if (lineView.line.gutterClass) { - var wrap = ensureLineWrapped(lineView); - lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass, - "left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + - "px; width: " + dims.gutterTotalWidth + "px"); - wrap.insertBefore(lineView.gutterBackground, lineView.text); - } - var markers = lineView.line.gutterMarkers; - if (cm.options.lineNumbers || markers) { - var wrap = ensureLineWrapped(lineView); - var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " + - (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"); - cm.display.input.setUneditable(gutterWrap); - wrap.insertBefore(gutterWrap, lineView.text); - if (lineView.line.gutterClass) - gutterWrap.className += " " + lineView.line.gutterClass; - if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) - lineView.lineNumber = gutterWrap.appendChild( - elt("div", lineNumberFor(cm.options, lineN), - "CodeMirror-linenumber CodeMirror-gutter-elt", - "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " - + cm.display.lineNumInnerWidth + "px")); - if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { - var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; - if (found) - gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + - dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); - } - } - } - - function updateLineWidgets(cm, lineView, dims) { - if (lineView.alignable) lineView.alignable = null; - for (var node = lineView.node.firstChild, next; node; node = next) { - var next = node.nextSibling; - if (node.className == "CodeMirror-linewidget") - lineView.node.removeChild(node); - } - insertLineWidgets(cm, lineView, dims); - } - - // Build a line's DOM representation from scratch - function buildLineElement(cm, lineView, lineN, dims) { - var built = getLineContent(cm, lineView); - lineView.text = lineView.node = built.pre; - if (built.bgClass) lineView.bgClass = built.bgClass; - if (built.textClass) lineView.textClass = built.textClass; - - updateLineClasses(lineView); - updateLineGutter(cm, lineView, lineN, dims); - insertLineWidgets(cm, lineView, dims); - return lineView.node; - } - - // A lineView may contain multiple logical lines (when merged by - // collapsed spans). The widgets for all of them need to be drawn. - function insertLineWidgets(cm, lineView, dims) { - insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); - if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) - insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); - } - - function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { - if (!line.widgets) return; - var wrap = ensureLineWrapped(lineView); - for (var i = 0, ws = line.widgets; i < ws.length; ++i) { - var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); - if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true"); - positionLineWidget(widget, node, lineView, dims); - cm.display.input.setUneditable(node); - if (allowAbove && widget.above) - wrap.insertBefore(node, lineView.gutter || lineView.text); - else - wrap.appendChild(node); - signalLater(widget, "redraw"); - } - } - - function positionLineWidget(widget, node, lineView, dims) { - if (widget.noHScroll) { - (lineView.alignable || (lineView.alignable = [])).push(node); - var width = dims.wrapperWidth; - node.style.left = dims.fixedPos + "px"; - if (!widget.coverGutter) { - width -= dims.gutterTotalWidth; - node.style.paddingLeft = dims.gutterTotalWidth + "px"; - } - node.style.width = width + "px"; - } - if (widget.coverGutter) { - node.style.zIndex = 5; - node.style.position = "relative"; - if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; - } - } - - // POSITION OBJECT - - // A Pos instance represents a position within the text. - var Pos = CodeMirror.Pos = function(line, ch) { - if (!(this instanceof Pos)) return new Pos(line, ch); - this.line = line; this.ch = ch; - }; - - // Compare two positions, return 0 if they are the same, a negative - // number when a is less, and a positive number otherwise. - var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; }; - - function copyPos(x) {return Pos(x.line, x.ch);} - function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } - function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } - - // INPUT HANDLING - - function ensureFocus(cm) { - if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } - } - - // This will be set to an array of strings when copying, so that, - // when pasting, we know what kind of selections the copied text - // was made out of. - var lastCopied = null; - - function applyTextInput(cm, inserted, deleted, sel, origin) { - var doc = cm.doc; - cm.display.shift = false; - if (!sel) sel = doc.sel; - - var paste = cm.state.pasteIncoming || origin == "paste"; - var textLines = doc.splitLines(inserted), multiPaste = null; - // When pasing N lines into N selections, insert one line per selection - if (paste && sel.ranges.length > 1) { - if (lastCopied && lastCopied.join("\n") == inserted) { - if (sel.ranges.length % lastCopied.length == 0) { - multiPaste = []; - for (var i = 0; i < lastCopied.length; i++) - multiPaste.push(doc.splitLines(lastCopied[i])); - } - } else if (textLines.length == sel.ranges.length) { - multiPaste = map(textLines, function(l) { return [l]; }); - } - } - - // Normal behavior is to insert the new text into every selection - for (var i = sel.ranges.length - 1; i >= 0; i--) { - var range = sel.ranges[i]; - var from = range.from(), to = range.to(); - if (range.empty()) { - if (deleted && deleted > 0) // Handle deletion - from = Pos(from.line, from.ch - deleted); - else if (cm.state.overwrite && !paste) // Handle overwrite - to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); - } - var updateInput = cm.curOp.updateInput; - var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, - origin: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")}; - makeChange(cm.doc, changeEvent); - signalLater(cm, "inputRead", cm, changeEvent); - } - if (inserted && !paste) - triggerElectric(cm, inserted); - - ensureCursorVisible(cm); - cm.curOp.updateInput = updateInput; - cm.curOp.typing = true; - cm.state.pasteIncoming = cm.state.cutIncoming = false; - } - - function handlePaste(e, cm) { - var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); - if (pasted) { - e.preventDefault(); - if (!cm.isReadOnly() && !cm.options.disableInput) - runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); }); - return true; - } - } - - function triggerElectric(cm, inserted) { - // When an 'electric' character is inserted, immediately trigger a reindent - if (!cm.options.electricChars || !cm.options.smartIndent) return; - var sel = cm.doc.sel; - - for (var i = sel.ranges.length - 1; i >= 0; i--) { - var range = sel.ranges[i]; - if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) continue; - var mode = cm.getModeAt(range.head); - var indented = false; - if (mode.electricChars) { - for (var j = 0; j < mode.electricChars.length; j++) - if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { - indented = indentLine(cm, range.head.line, "smart"); - break; - } - } else if (mode.electricInput) { - if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) - indented = indentLine(cm, range.head.line, "smart"); - } - if (indented) signalLater(cm, "electricInput", cm, range.head.line); - } - } - - function copyableRanges(cm) { - var text = [], ranges = []; - for (var i = 0; i < cm.doc.sel.ranges.length; i++) { - var line = cm.doc.sel.ranges[i].head.line; - var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; - ranges.push(lineRange); - text.push(cm.getRange(lineRange.anchor, lineRange.head)); - } - return {text: text, ranges: ranges}; - } - - function disableBrowserMagic(field) { - field.setAttribute("autocorrect", "off"); - field.setAttribute("autocapitalize", "off"); - field.setAttribute("spellcheck", "false"); - } - - // TEXTAREA INPUT STYLE - - function TextareaInput(cm) { - this.cm = cm; - // See input.poll and input.reset - this.prevInput = ""; - - // Flag that indicates whether we expect input to appear real soon - // now (after some event like 'keypress' or 'input') and are - // polling intensively. - this.pollingFast = false; - // Self-resetting timeout for the poller - this.polling = new Delayed(); - // Tracks when input.reset has punted to just putting a short - // string into the textarea instead of the full selection. - this.inaccurateSelection = false; - // Used to work around IE issue with selection being forgotten when focus moves away from textarea - this.hasSelection = false; - this.composing = null; - }; - - function hiddenTextarea() { - var te = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none"); - var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); - // The textarea is kept positioned near the cursor to prevent the - // fact that it'll be scrolled into view on input from scrolling - // our fake cursor out of view. On webkit, when wrap=off, paste is - // very slow. So make the area wide instead. - if (webkit) te.style.width = "1000px"; - else te.setAttribute("wrap", "off"); - // If border: 0; -- iOS fails to open keyboard (issue #1287) - if (ios) te.style.border = "1px solid black"; - disableBrowserMagic(te); - return div; - } - - TextareaInput.prototype = copyObj({ - init: function(display) { - var input = this, cm = this.cm; - - // Wraps and hides input textarea - var div = this.wrapper = hiddenTextarea(); - // The semihidden textarea that is focused when the editor is - // focused, and receives input. - var te = this.textarea = div.firstChild; - display.wrapper.insertBefore(div, display.wrapper.firstChild); - - // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) - if (ios) te.style.width = "0px"; - - on(te, "input", function() { - if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null; - input.poll(); - }); - - on(te, "paste", function(e) { - if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return - - cm.state.pasteIncoming = true; - input.fastPoll(); - }); - - function prepareCopyCut(e) { - if (signalDOMEvent(cm, e)) return - if (cm.somethingSelected()) { - lastCopied = cm.getSelections(); - if (input.inaccurateSelection) { - input.prevInput = ""; - input.inaccurateSelection = false; - te.value = lastCopied.join("\n"); - selectInput(te); - } - } else if (!cm.options.lineWiseCopyCut) { - return; - } else { - var ranges = copyableRanges(cm); - lastCopied = ranges.text; - if (e.type == "cut") { - cm.setSelections(ranges.ranges, null, sel_dontScroll); - } else { - input.prevInput = ""; - te.value = ranges.text.join("\n"); - selectInput(te); - } - } - if (e.type == "cut") cm.state.cutIncoming = true; - } - on(te, "cut", prepareCopyCut); - on(te, "copy", prepareCopyCut); - - on(display.scroller, "paste", function(e) { - if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return; - cm.state.pasteIncoming = true; - input.focus(); - }); - - // Prevent normal selection in the editor (we handle our own) - on(display.lineSpace, "selectstart", function(e) { - if (!eventInWidget(display, e)) e_preventDefault(e); - }); - - on(te, "compositionstart", function() { - var start = cm.getCursor("from"); - if (input.composing) input.composing.range.clear() - input.composing = { - start: start, - range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) - }; - }); - on(te, "compositionend", function() { - if (input.composing) { - input.poll(); - input.composing.range.clear(); - input.composing = null; - } - }); - }, - - prepareSelection: function() { - // Redraw the selection and/or cursor - var cm = this.cm, display = cm.display, doc = cm.doc; - var result = prepareSelection(cm); - - // Move the hidden textarea near the cursor to prevent scrolling artifacts - if (cm.options.moveInputWithCursor) { - var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); - var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); - result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, - headPos.top + lineOff.top - wrapOff.top)); - result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, - headPos.left + lineOff.left - wrapOff.left)); - } - - return result; - }, - - showSelection: function(drawn) { - var cm = this.cm, display = cm.display; - removeChildrenAndAdd(display.cursorDiv, drawn.cursors); - removeChildrenAndAdd(display.selectionDiv, drawn.selection); - if (drawn.teTop != null) { - this.wrapper.style.top = drawn.teTop + "px"; - this.wrapper.style.left = drawn.teLeft + "px"; - } - }, - - // Reset the input to correspond to the selection (or to be empty, - // when not typing and nothing is selected) - reset: function(typing) { - if (this.contextMenuPending) return; - var minimal, selected, cm = this.cm, doc = cm.doc; - if (cm.somethingSelected()) { - this.prevInput = ""; - var range = doc.sel.primary(); - minimal = hasCopyEvent && - (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000); - var content = minimal ? "-" : selected || cm.getSelection(); - this.textarea.value = content; - if (cm.state.focused) selectInput(this.textarea); - if (ie && ie_version >= 9) this.hasSelection = content; - } else if (!typing) { - this.prevInput = this.textarea.value = ""; - if (ie && ie_version >= 9) this.hasSelection = null; - } - this.inaccurateSelection = minimal; - }, - - getField: function() { return this.textarea; }, - - supportsTouch: function() { return false; }, - - focus: function() { - if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { - try { this.textarea.focus(); } - catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM - } - }, - - blur: function() { this.textarea.blur(); }, - - resetPosition: function() { - this.wrapper.style.top = this.wrapper.style.left = 0; - }, - - receivedFocus: function() { this.slowPoll(); }, - - // Poll for input changes, using the normal rate of polling. This - // runs as long as the editor is focused. - slowPoll: function() { - var input = this; - if (input.pollingFast) return; - input.polling.set(this.cm.options.pollInterval, function() { - input.poll(); - if (input.cm.state.focused) input.slowPoll(); - }); - }, - - // When an event has just come in that is likely to add or change - // something in the input textarea, we poll faster, to ensure that - // the change appears on the screen quickly. - fastPoll: function() { - var missed = false, input = this; - input.pollingFast = true; - function p() { - var changed = input.poll(); - if (!changed && !missed) {missed = true; input.polling.set(60, p);} - else {input.pollingFast = false; input.slowPoll();} - } - input.polling.set(20, p); - }, - - // Read input from the textarea, and update the document to match. - // When something is selected, it is present in the textarea, and - // selected (unless it is huge, in which case a placeholder is - // used). When nothing is selected, the cursor sits after previously - // seen text (can be empty), which is stored in prevInput (we must - // not reset the textarea when typing, because that breaks IME). - poll: function() { - var cm = this.cm, input = this.textarea, prevInput = this.prevInput; - // Since this is called a *lot*, try to bail out as cheaply as - // possible when it is clear that nothing happened. hasSelection - // will be the case when there is a lot of text in the textarea, - // in which case reading its value would be expensive. - if (this.contextMenuPending || !cm.state.focused || - (hasSelection(input) && !prevInput && !this.composing) || - cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) - return false; - - var text = input.value; - // If nothing changed, bail. - if (text == prevInput && !cm.somethingSelected()) return false; - // Work around nonsensical selection resetting in IE9/10, and - // inexplicable appearance of private area unicode characters on - // some key combos in Mac (#2689). - if (ie && ie_version >= 9 && this.hasSelection === text || - mac && /[\uf700-\uf7ff]/.test(text)) { - cm.display.input.reset(); - return false; - } - - if (cm.doc.sel == cm.display.selForContextMenu) { - var first = text.charCodeAt(0); - if (first == 0x200b && !prevInput) prevInput = "\u200b"; - if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); } - } - // Find the part of the input that is actually new - var same = 0, l = Math.min(prevInput.length, text.length); - while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; - - var self = this; - runInOp(cm, function() { - applyTextInput(cm, text.slice(same), prevInput.length - same, - null, self.composing ? "*compose" : null); - - // Don't leave long text in the textarea, since it makes further polling slow - if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = ""; - else self.prevInput = text; - - if (self.composing) { - self.composing.range.clear(); - self.composing.range = cm.markText(self.composing.start, cm.getCursor("to"), - {className: "CodeMirror-composing"}); - } - }); - return true; - }, - - ensurePolled: function() { - if (this.pollingFast && this.poll()) this.pollingFast = false; - }, - - onKeyPress: function() { - if (ie && ie_version >= 9) this.hasSelection = null; - this.fastPoll(); - }, - - onContextMenu: function(e) { - var input = this, cm = input.cm, display = cm.display, te = input.textarea; - var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; - if (!pos || presto) return; // Opera is difficult. - - // Reset the current text selection only if the click is done outside of the selection - // and 'resetSelectionOnContextMenu' option is true. - var reset = cm.options.resetSelectionOnContextMenu; - if (reset && cm.doc.sel.contains(pos) == -1) - operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); - - var oldCSS = te.style.cssText; - input.wrapper.style.position = "absolute"; - te.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + - "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + - (ie ? "rgba(255, 255, 255, .05)" : "transparent") + - "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; - if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) - display.input.focus(); - if (webkit) window.scrollTo(null, oldScrollY); - display.input.reset(); - // Adds "Select all" to context menu in FF - if (!cm.somethingSelected()) te.value = input.prevInput = " "; - input.contextMenuPending = true; - display.selForContextMenu = cm.doc.sel; - clearTimeout(display.detectingSelectAll); - - // Select-all will be greyed out if there's nothing to select, so - // this adds a zero-width space so that we can later check whether - // it got selected. - function prepareSelectAllHack() { - if (te.selectionStart != null) { - var selected = cm.somethingSelected(); - var extval = "\u200b" + (selected ? te.value : ""); - te.value = "\u21da"; // Used to catch context-menu undo - te.value = extval; - input.prevInput = selected ? "" : "\u200b"; - te.selectionStart = 1; te.selectionEnd = extval.length; - // Re-set this, in case some other handler touched the - // selection in the meantime. - display.selForContextMenu = cm.doc.sel; - } - } - function rehide() { - input.contextMenuPending = false; - input.wrapper.style.position = "relative"; - te.style.cssText = oldCSS; - if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); - - // Try to detect the user choosing select-all - if (te.selectionStart != null) { - if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); - var i = 0, poll = function() { - if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && - te.selectionEnd > 0 && input.prevInput == "\u200b") - operation(cm, commands.selectAll)(cm); - else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); - else display.input.reset(); - }; - display.detectingSelectAll = setTimeout(poll, 200); - } - } - - if (ie && ie_version >= 9) prepareSelectAllHack(); - if (captureRightClick) { - e_stop(e); - var mouseup = function() { - off(window, "mouseup", mouseup); - setTimeout(rehide, 20); - }; - on(window, "mouseup", mouseup); - } else { - setTimeout(rehide, 50); - } - }, - - readOnlyChanged: function(val) { - if (!val) this.reset(); - }, - - setUneditable: nothing, - - needsContentAttribute: false - }, TextareaInput.prototype); - - // CONTENTEDITABLE INPUT STYLE - - function ContentEditableInput(cm) { - this.cm = cm; - this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; - this.polling = new Delayed(); - this.gracePeriod = false; - } - - ContentEditableInput.prototype = copyObj({ - init: function(display) { - var input = this, cm = input.cm; - var div = input.div = display.lineDiv; - disableBrowserMagic(div); - - on(div, "paste", function(e) { - if (!signalDOMEvent(cm, e)) handlePaste(e, cm); - }) - - on(div, "compositionstart", function(e) { - var data = e.data; - input.composing = {sel: cm.doc.sel, data: data, startData: data}; - if (!data) return; - var prim = cm.doc.sel.primary(); - var line = cm.getLine(prim.head.line); - var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length)); - if (found > -1 && found <= prim.head.ch) - input.composing.sel = simpleSelection(Pos(prim.head.line, found), - Pos(prim.head.line, found + data.length)); - }); - on(div, "compositionupdate", function(e) { - input.composing.data = e.data; - }); - on(div, "compositionend", function(e) { - var ours = input.composing; - if (!ours) return; - if (e.data != ours.startData && !/\u200b/.test(e.data)) - ours.data = e.data; - // Need a small delay to prevent other code (input event, - // selection polling) from doing damage when fired right after - // compositionend. - setTimeout(function() { - if (!ours.handled) - input.applyComposition(ours); - if (input.composing == ours) - input.composing = null; - }, 50); - }); - - on(div, "touchstart", function() { - input.forceCompositionEnd(); - }); - - on(div, "input", function() { - if (input.composing) return; - if (cm.isReadOnly() || !input.pollContent()) - runInOp(input.cm, function() {regChange(cm);}); - }); - - function onCopyCut(e) { - if (signalDOMEvent(cm, e)) return - if (cm.somethingSelected()) { - lastCopied = cm.getSelections(); - if (e.type == "cut") cm.replaceSelection("", null, "cut"); - } else if (!cm.options.lineWiseCopyCut) { - return; - } else { - var ranges = copyableRanges(cm); - lastCopied = ranges.text; - if (e.type == "cut") { - cm.operation(function() { - cm.setSelections(ranges.ranges, 0, sel_dontScroll); - cm.replaceSelection("", null, "cut"); - }); - } - } - // iOS exposes the clipboard API, but seems to discard content inserted into it - if (e.clipboardData && !ios) { - e.preventDefault(); - e.clipboardData.clearData(); - e.clipboardData.setData("text/plain", lastCopied.join("\n")); - } else { - // Old-fashioned briefly-focus-a-textarea hack - var kludge = hiddenTextarea(), te = kludge.firstChild; - cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); - te.value = lastCopied.join("\n"); - var hadFocus = document.activeElement; - selectInput(te); - setTimeout(function() { - cm.display.lineSpace.removeChild(kludge); - hadFocus.focus(); - }, 50); - } - } - on(div, "copy", onCopyCut); - on(div, "cut", onCopyCut); - }, - - prepareSelection: function() { - var result = prepareSelection(this.cm, false); - result.focus = this.cm.state.focused; - return result; - }, - - showSelection: function(info) { - if (!info || !this.cm.display.view.length) return; - if (info.focus) this.showPrimarySelection(); - this.showMultipleSelections(info); - }, - - showPrimarySelection: function() { - var sel = window.getSelection(), prim = this.cm.doc.sel.primary(); - var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset); - var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset); - if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && - cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && - cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) - return; - - var start = posToDOM(this.cm, prim.from()); - var end = posToDOM(this.cm, prim.to()); - if (!start && !end) return; - - var view = this.cm.display.view; - var old = sel.rangeCount && sel.getRangeAt(0); - if (!start) { - start = {node: view[0].measure.map[2], offset: 0}; - } else if (!end) { // FIXME dangerously hacky - var measure = view[view.length - 1].measure; - var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; - end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}; - } - - try { var rng = range(start.node, start.offset, end.offset, end.node); } - catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible - if (rng) { - if (!gecko && this.cm.state.focused) { - sel.collapse(start.node, start.offset); - if (!rng.collapsed) sel.addRange(rng); - } else { - sel.removeAllRanges(); - sel.addRange(rng); - } - if (old && sel.anchorNode == null) sel.addRange(old); - else if (gecko) this.startGracePeriod(); - } - this.rememberSelection(); - }, - - startGracePeriod: function() { - var input = this; - clearTimeout(this.gracePeriod); - this.gracePeriod = setTimeout(function() { - input.gracePeriod = false; - if (input.selectionChanged()) - input.cm.operation(function() { input.cm.curOp.selectionChanged = true; }); - }, 20); - }, - - showMultipleSelections: function(info) { - removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); - removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); - }, - - rememberSelection: function() { - var sel = window.getSelection(); - this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; - this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; - }, - - selectionInEditor: function() { - var sel = window.getSelection(); - if (!sel.rangeCount) return false; - var node = sel.getRangeAt(0).commonAncestorContainer; - return contains(this.div, node); - }, - - focus: function() { - if (this.cm.options.readOnly != "nocursor") this.div.focus(); - }, - blur: function() { this.div.blur(); }, - getField: function() { return this.div; }, - - supportsTouch: function() { return true; }, - - receivedFocus: function() { - var input = this; - if (this.selectionInEditor()) - this.pollSelection(); - else - runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; }); - - function poll() { - if (input.cm.state.focused) { - input.pollSelection(); - input.polling.set(input.cm.options.pollInterval, poll); - } - } - this.polling.set(this.cm.options.pollInterval, poll); - }, - - selectionChanged: function() { - var sel = window.getSelection(); - return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || - sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset; - }, - - pollSelection: function() { - if (!this.composing && !this.gracePeriod && this.selectionChanged()) { - var sel = window.getSelection(), cm = this.cm; - this.rememberSelection(); - var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); - var head = domToPos(cm, sel.focusNode, sel.focusOffset); - if (anchor && head) runInOp(cm, function() { - setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); - if (anchor.bad || head.bad) cm.curOp.selectionChanged = true; - }); - } - }, - - pollContent: function() { - var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); - var from = sel.from(), to = sel.to(); - if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false; - - var fromIndex; - if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { - var fromLine = lineNo(display.view[0].line); - var fromNode = display.view[0].node; - } else { - var fromLine = lineNo(display.view[fromIndex].line); - var fromNode = display.view[fromIndex - 1].node.nextSibling; - } - var toIndex = findViewIndex(cm, to.line); - if (toIndex == display.view.length - 1) { - var toLine = display.viewTo - 1; - var toNode = display.lineDiv.lastChild; - } else { - var toLine = lineNo(display.view[toIndex + 1].line) - 1; - var toNode = display.view[toIndex + 1].node.previousSibling; - } - - var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); - var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); - while (newText.length > 1 && oldText.length > 1) { - if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } - else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } - else break; - } - - var cutFront = 0, cutEnd = 0; - var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); - while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) - ++cutFront; - var newBot = lst(newText), oldBot = lst(oldText); - var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), - oldBot.length - (oldText.length == 1 ? cutFront : 0)); - while (cutEnd < maxCutEnd && - newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) - ++cutEnd; - - newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd); - newText[0] = newText[0].slice(cutFront); - - var chFrom = Pos(fromLine, cutFront); - var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); - if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { - replaceRange(cm.doc, newText, chFrom, chTo, "+input"); - return true; - } - }, - - ensurePolled: function() { - this.forceCompositionEnd(); - }, - reset: function() { - this.forceCompositionEnd(); - }, - forceCompositionEnd: function() { - if (!this.composing || this.composing.handled) return; - this.applyComposition(this.composing); - this.composing.handled = true; - this.div.blur(); - this.div.focus(); - }, - applyComposition: function(composing) { - if (this.cm.isReadOnly()) - operation(this.cm, regChange)(this.cm) - else if (composing.data && composing.data != composing.startData) - operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel); - }, - - setUneditable: function(node) { - node.contentEditable = "false" - }, - - onKeyPress: function(e) { - e.preventDefault(); - if (!this.cm.isReadOnly()) - operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); - }, - - readOnlyChanged: function(val) { - this.div.contentEditable = String(val != "nocursor") - }, - - onContextMenu: nothing, - resetPosition: nothing, - - needsContentAttribute: true - }, ContentEditableInput.prototype); - - function posToDOM(cm, pos) { - var view = findViewForLine(cm, pos.line); - if (!view || view.hidden) return null; - var line = getLine(cm.doc, pos.line); - var info = mapFromLineView(view, line, pos.line); - - var order = getOrder(line), side = "left"; - if (order) { - var partPos = getBidiPartAt(order, pos.ch); - side = partPos % 2 ? "right" : "left"; - } - var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); - result.offset = result.collapse == "right" ? result.end : result.start; - return result; - } - - function badPos(pos, bad) { if (bad) pos.bad = true; return pos; } - - function domToPos(cm, node, offset) { - var lineNode; - if (node == cm.display.lineDiv) { - lineNode = cm.display.lineDiv.childNodes[offset]; - if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true); - node = null; offset = 0; - } else { - for (lineNode = node;; lineNode = lineNode.parentNode) { - if (!lineNode || lineNode == cm.display.lineDiv) return null; - if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break; - } - } - for (var i = 0; i < cm.display.view.length; i++) { - var lineView = cm.display.view[i]; - if (lineView.node == lineNode) - return locateNodeInLineView(lineView, node, offset); - } - } - - function locateNodeInLineView(lineView, node, offset) { - var wrapper = lineView.text.firstChild, bad = false; - if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true); - if (node == wrapper) { - bad = true; - node = wrapper.childNodes[offset]; - offset = 0; - if (!node) { - var line = lineView.rest ? lst(lineView.rest) : lineView.line; - return badPos(Pos(lineNo(line), line.text.length), bad); - } - } - - var textNode = node.nodeType == 3 ? node : null, topNode = node; - if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { - textNode = node.firstChild; - if (offset) offset = textNode.nodeValue.length; - } - while (topNode.parentNode != wrapper) topNode = topNode.parentNode; - var measure = lineView.measure, maps = measure.maps; - - function find(textNode, topNode, offset) { - for (var i = -1; i < (maps ? maps.length : 0); i++) { - var map = i < 0 ? measure.map : maps[i]; - for (var j = 0; j < map.length; j += 3) { - var curNode = map[j + 2]; - if (curNode == textNode || curNode == topNode) { - var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); - var ch = map[j] + offset; - if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)]; - return Pos(line, ch); - } - } - } - } - var found = find(textNode, topNode, offset); - if (found) return badPos(found, bad); - - // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems - for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { - found = find(after, after.firstChild, 0); - if (found) - return badPos(Pos(found.line, found.ch - dist), bad); - else - dist += after.textContent.length; - } - for (var before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) { - found = find(before, before.firstChild, -1); - if (found) - return badPos(Pos(found.line, found.ch + dist), bad); - else - dist += after.textContent.length; - } - } - - function domTextBetween(cm, from, to, fromLine, toLine) { - var text = "", closing = false, lineSep = cm.doc.lineSeparator(); - function recognizeMarker(id) { return function(marker) { return marker.id == id; }; } - function walk(node) { - if (node.nodeType == 1) { - var cmText = node.getAttribute("cm-text"); - if (cmText != null) { - if (cmText == "") cmText = node.textContent.replace(/\u200b/g, ""); - text += cmText; - return; - } - var markerID = node.getAttribute("cm-marker"), range; - if (markerID) { - var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); - if (found.length && (range = found[0].find())) - text += getBetween(cm.doc, range.from, range.to).join(lineSep); - return; - } - if (node.getAttribute("contenteditable") == "false") return; - for (var i = 0; i < node.childNodes.length; i++) - walk(node.childNodes[i]); - if (/^(pre|div|p)$/i.test(node.nodeName)) - closing = true; - } else if (node.nodeType == 3) { - var val = node.nodeValue; - if (!val) return; - if (closing) { - text += lineSep; - closing = false; - } - text += val; - } - } - for (;;) { - walk(from); - if (from == to) break; - from = from.nextSibling; - } - return text; - } - - CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; - - // SELECTION / CURSOR - - // Selection objects are immutable. A new one is created every time - // the selection changes. A selection is one or more non-overlapping - // (and non-touching) ranges, sorted, and an integer that indicates - // which one is the primary selection (the one that's scrolled into - // view, that getCursor returns, etc). - function Selection(ranges, primIndex) { - this.ranges = ranges; - this.primIndex = primIndex; - } - - Selection.prototype = { - primary: function() { return this.ranges[this.primIndex]; }, - equals: function(other) { - if (other == this) return true; - if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false; - for (var i = 0; i < this.ranges.length; i++) { - var here = this.ranges[i], there = other.ranges[i]; - if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false; - } - return true; - }, - deepCopy: function() { - for (var out = [], i = 0; i < this.ranges.length; i++) - out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); - return new Selection(out, this.primIndex); - }, - somethingSelected: function() { - for (var i = 0; i < this.ranges.length; i++) - if (!this.ranges[i].empty()) return true; - return false; - }, - contains: function(pos, end) { - if (!end) end = pos; - for (var i = 0; i < this.ranges.length; i++) { - var range = this.ranges[i]; - if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) - return i; - } - return -1; - } - }; - - function Range(anchor, head) { - this.anchor = anchor; this.head = head; - } - - Range.prototype = { - from: function() { return minPos(this.anchor, this.head); }, - to: function() { return maxPos(this.anchor, this.head); }, - empty: function() { - return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch; - } - }; - - // Take an unsorted, potentially overlapping set of ranges, and - // build a selection out of it. 'Consumes' ranges array (modifying - // it). - function normalizeSelection(ranges, primIndex) { - var prim = ranges[primIndex]; - ranges.sort(function(a, b) { return cmp(a.from(), b.from()); }); - primIndex = indexOf(ranges, prim); - for (var i = 1; i < ranges.length; i++) { - var cur = ranges[i], prev = ranges[i - 1]; - if (cmp(prev.to(), cur.from()) >= 0) { - var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); - var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; - if (i <= primIndex) --primIndex; - ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); - } - } - return new Selection(ranges, primIndex); - } - - function simpleSelection(anchor, head) { - return new Selection([new Range(anchor, head || anchor)], 0); - } - - // Most of the external API clips given positions to make sure they - // actually exist within the document. - function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} - function clipPos(doc, pos) { - if (pos.line < doc.first) return Pos(doc.first, 0); - var last = doc.first + doc.size - 1; - if (pos.line > last) return Pos(last, getLine(doc, last).text.length); - return clipToLen(pos, getLine(doc, pos.line).text.length); - } - function clipToLen(pos, linelen) { - var ch = pos.ch; - if (ch == null || ch > linelen) return Pos(pos.line, linelen); - else if (ch < 0) return Pos(pos.line, 0); - else return pos; - } - function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} - function clipPosArray(doc, array) { - for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]); - return out; - } - - // SELECTION UPDATES - - // The 'scroll' parameter given to many of these indicated whether - // the new cursor position should be scrolled into view after - // modifying the selection. - - // If shift is held or the extend flag is set, extends a range to - // include a given position (and optionally a second position). - // Otherwise, simply returns the range between the given positions. - // Used for cursor motion and such. - function extendRange(doc, range, head, other) { - if (doc.cm && doc.cm.display.shift || doc.extend) { - var anchor = range.anchor; - if (other) { - var posBefore = cmp(head, anchor) < 0; - if (posBefore != (cmp(other, anchor) < 0)) { - anchor = head; - head = other; - } else if (posBefore != (cmp(head, other) < 0)) { - head = other; - } - } - return new Range(anchor, head); - } else { - return new Range(other || head, head); - } - } - - // Extend the primary selection range, discard the rest. - function extendSelection(doc, head, other, options) { - setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options); - } - - // Extend all selections (pos is an array of selections with length - // equal the number of selections) - function extendSelections(doc, heads, options) { - for (var out = [], i = 0; i < doc.sel.ranges.length; i++) - out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null); - var newSel = normalizeSelection(out, doc.sel.primIndex); - setSelection(doc, newSel, options); - } - - // Updates a single range in the selection. - function replaceOneSelection(doc, i, range, options) { - var ranges = doc.sel.ranges.slice(0); - ranges[i] = range; - setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); - } - - // Reset the selection to a single range. - function setSimpleSelection(doc, anchor, head, options) { - setSelection(doc, simpleSelection(anchor, head), options); - } - - // Give beforeSelectionChange handlers a change to influence a - // selection update. - function filterSelectionChange(doc, sel, options) { - var obj = { - ranges: sel.ranges, - update: function(ranges) { - this.ranges = []; - for (var i = 0; i < ranges.length; i++) - this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), - clipPos(doc, ranges[i].head)); - }, - origin: options && options.origin - }; - signal(doc, "beforeSelectionChange", doc, obj); - if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); - if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1); - else return sel; - } - - function setSelectionReplaceHistory(doc, sel, options) { - var done = doc.history.done, last = lst(done); - if (last && last.ranges) { - done[done.length - 1] = sel; - setSelectionNoUndo(doc, sel, options); - } else { - setSelection(doc, sel, options); - } - } - - // Set a new selection. - function setSelection(doc, sel, options) { - setSelectionNoUndo(doc, sel, options); - addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); - } - - function setSelectionNoUndo(doc, sel, options) { - if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) - sel = filterSelectionChange(doc, sel, options); - - var bias = options && options.bias || - (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); - setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); - - if (!(options && options.scroll === false) && doc.cm) - ensureCursorVisible(doc.cm); - } - - function setSelectionInner(doc, sel) { - if (sel.equals(doc.sel)) return; - - doc.sel = sel; - - if (doc.cm) { - doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; - signalCursorActivity(doc.cm); - } - signalLater(doc, "cursorActivity", doc); - } - - // Verify that the selection does not partially select any atomic - // marked ranges. - function reCheckSelection(doc) { - setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll); - } - - // Return a selection that does not partially select any atomic - // ranges. - function skipAtomicInSelection(doc, sel, bias, mayClear) { - var out; - for (var i = 0; i < sel.ranges.length; i++) { - var range = sel.ranges[i]; - var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i]; - var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear); - var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear); - if (out || newAnchor != range.anchor || newHead != range.head) { - if (!out) out = sel.ranges.slice(0, i); - out[i] = new Range(newAnchor, newHead); - } - } - return out ? normalizeSelection(out, sel.primIndex) : sel; - } - - function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { - var line = getLine(doc, pos.line); - if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { - var sp = line.markedSpans[i], m = sp.marker; - if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && - (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { - if (mayClear) { - signal(m, "beforeCursorEnter"); - if (m.explicitlyCleared) { - if (!line.markedSpans) break; - else {--i; continue;} - } - } - if (!m.atomic) continue; - - if (oldPos) { - var near = m.find(dir < 0 ? 1 : -1), diff; - if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) near = movePos(doc, near, -dir, line); - if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) - return skipAtomicInner(doc, near, pos, dir, mayClear); - } - - var far = m.find(dir < 0 ? -1 : 1); - if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) far = movePos(doc, far, dir, line); - return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null; - } - } - return pos; - } - - // Ensure a given position is not inside an atomic range. - function skipAtomic(doc, pos, oldPos, bias, mayClear) { - var dir = bias || 1; - var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || - (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || - skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || - (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)); - if (!found) { - doc.cantEdit = true; - return Pos(doc.first, 0); - } - return found; - } - - function movePos(doc, pos, dir, line) { - if (dir < 0 && pos.ch == 0) { - if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1)); - else return null; - } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { - if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0); - else return null; - } else { - return new Pos(pos.line, pos.ch + dir); - } - } - - // SELECTION DRAWING - - function updateSelection(cm) { - cm.display.input.showSelection(cm.display.input.prepareSelection()); - } - - function prepareSelection(cm, primary) { - var doc = cm.doc, result = {}; - var curFragment = result.cursors = document.createDocumentFragment(); - var selFragment = result.selection = document.createDocumentFragment(); - - for (var i = 0; i < doc.sel.ranges.length; i++) { - if (primary === false && i == doc.sel.primIndex) continue; - var range = doc.sel.ranges[i]; - var collapsed = range.empty(); - if (collapsed || cm.options.showCursorWhenSelecting) - drawSelectionCursor(cm, range.head, curFragment); - if (!collapsed) - drawSelectionRange(cm, range, selFragment); - } - return result; - } - - // Draws a cursor for the given range - function drawSelectionCursor(cm, head, output) { - var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine); - - var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); - cursor.style.left = pos.left + "px"; - cursor.style.top = pos.top + "px"; - cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; - - if (pos.other) { - // Secondary cursor, shown when on a 'jump' in bi-directional text - var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); - otherCursor.style.display = ""; - otherCursor.style.left = pos.other.left + "px"; - otherCursor.style.top = pos.other.top + "px"; - otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; - } - } - - // Draws the given range as a highlighted selection - function drawSelectionRange(cm, range, output) { - var display = cm.display, doc = cm.doc; - var fragment = document.createDocumentFragment(); - var padding = paddingH(cm.display), leftSide = padding.left; - var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; - - function add(left, top, width, bottom) { - if (top < 0) top = 0; - top = Math.round(top); - bottom = Math.round(bottom); - fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + - "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) + - "px; height: " + (bottom - top) + "px")); - } - - function drawForLine(line, fromArg, toArg) { - var lineObj = getLine(doc, line); - var lineLen = lineObj.text.length; - var start, end; - function coords(ch, bias) { - return charCoords(cm, Pos(line, ch), "div", lineObj, bias); - } - - iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { - var leftPos = coords(from, "left"), rightPos, left, right; - if (from == to) { - rightPos = leftPos; - left = right = leftPos.left; - } else { - rightPos = coords(to - 1, "right"); - if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } - left = leftPos.left; - right = rightPos.right; - } - if (fromArg == null && from == 0) left = leftSide; - if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part - add(left, leftPos.top, null, leftPos.bottom); - left = leftSide; - if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); - } - if (toArg == null && to == lineLen) right = rightSide; - if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) - start = leftPos; - if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) - end = rightPos; - if (left < leftSide + 1) left = leftSide; - add(left, rightPos.top, right - left, rightPos.bottom); - }); - return {start: start, end: end}; - } - - var sFrom = range.from(), sTo = range.to(); - if (sFrom.line == sTo.line) { - drawForLine(sFrom.line, sFrom.ch, sTo.ch); - } else { - var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); - var singleVLine = visualLine(fromLine) == visualLine(toLine); - var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; - var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; - if (singleVLine) { - if (leftEnd.top < rightStart.top - 2) { - add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); - add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); - } else { - add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); - } - } - if (leftEnd.bottom < rightStart.top) - add(leftSide, leftEnd.bottom, null, rightStart.top); - } - - output.appendChild(fragment); - } - - // Cursor-blinking - function restartBlink(cm) { - if (!cm.state.focused) return; - var display = cm.display; - clearInterval(display.blinker); - var on = true; - display.cursorDiv.style.visibility = ""; - if (cm.options.cursorBlinkRate > 0) - display.blinker = setInterval(function() { - display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; - }, cm.options.cursorBlinkRate); - else if (cm.options.cursorBlinkRate < 0) - display.cursorDiv.style.visibility = "hidden"; - } - - // HIGHLIGHT WORKER - - function startWorker(cm, time) { - if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) - cm.state.highlight.set(time, bind(highlightWorker, cm)); - } - - function highlightWorker(cm) { - var doc = cm.doc; - if (doc.frontier < doc.first) doc.frontier = doc.first; - if (doc.frontier >= cm.display.viewTo) return; - var end = +new Date + cm.options.workTime; - var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); - var changedLines = []; - - doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { - if (doc.frontier >= cm.display.viewFrom) { // Visible - var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHighlightLength; - var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, state) : state, true); - line.styles = highlighted.styles; - var oldCls = line.styleClasses, newCls = highlighted.classes; - if (newCls) line.styleClasses = newCls; - else if (oldCls) line.styleClasses = null; - var ischange = !oldStyles || oldStyles.length != line.styles.length || - oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); - for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; - if (ischange) changedLines.push(doc.frontier); - line.stateAfter = tooLong ? state : copyState(doc.mode, state); - } else { - if (line.text.length <= cm.options.maxHighlightLength) - processLine(cm, line.text, state); - line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; - } - ++doc.frontier; - if (+new Date > end) { - startWorker(cm, cm.options.workDelay); - return true; - } - }); - if (changedLines.length) runInOp(cm, function() { - for (var i = 0; i < changedLines.length; i++) - regLineChange(cm, changedLines[i], "text"); - }); - } - - // Finds the line to start with when starting a parse. Tries to - // find a line with a stateAfter, so that it can start with a - // valid state. If that fails, it returns the line with the - // smallest indentation, which tends to need the least context to - // parse correctly. - function findStartLine(cm, n, precise) { - var minindent, minline, doc = cm.doc; - var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); - for (var search = n; search > lim; --search) { - if (search <= doc.first) return doc.first; - var line = getLine(doc, search - 1); - if (line.stateAfter && (!precise || search <= doc.frontier)) return search; - var indented = countColumn(line.text, null, cm.options.tabSize); - if (minline == null || minindent > indented) { - minline = search - 1; - minindent = indented; - } - } - return minline; - } - - function getStateBefore(cm, n, precise) { - var doc = cm.doc, display = cm.display; - if (!doc.mode.startState) return true; - var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; - if (!state) state = startState(doc.mode); - else state = copyState(doc.mode, state); - doc.iter(pos, n, function(line) { - processLine(cm, line.text, state); - var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo; - line.stateAfter = save ? copyState(doc.mode, state) : null; - ++pos; - }); - if (precise) doc.frontier = pos; - return state; - } - - // POSITION MEASUREMENT - - function paddingTop(display) {return display.lineSpace.offsetTop;} - function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} - function paddingH(display) { - if (display.cachedPaddingH) return display.cachedPaddingH; - var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); - var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; - var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; - if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; - return data; - } - - function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; } - function displayWidth(cm) { - return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth; - } - function displayHeight(cm) { - return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight; - } - - // Ensure the lineView.wrapping.heights array is populated. This is - // an array of bottom offsets for the lines that make up a drawn - // line. When lineWrapping is on, there might be more than one - // height. - function ensureLineHeights(cm, lineView, rect) { - var wrapping = cm.options.lineWrapping; - var curWidth = wrapping && displayWidth(cm); - if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { - var heights = lineView.measure.heights = []; - if (wrapping) { - lineView.measure.width = curWidth; - var rects = lineView.text.firstChild.getClientRects(); - for (var i = 0; i < rects.length - 1; i++) { - var cur = rects[i], next = rects[i + 1]; - if (Math.abs(cur.bottom - next.bottom) > 2) - heights.push((cur.bottom + next.top) / 2 - rect.top); - } - } - heights.push(rect.bottom - rect.top); - } - } - - // Find a line map (mapping character offsets to text nodes) and a - // measurement cache for the given line number. (A line view might - // contain multiple lines when collapsed ranges are present.) - function mapFromLineView(lineView, line, lineN) { - if (lineView.line == line) - return {map: lineView.measure.map, cache: lineView.measure.cache}; - for (var i = 0; i < lineView.rest.length; i++) - if (lineView.rest[i] == line) - return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]}; - for (var i = 0; i < lineView.rest.length; i++) - if (lineNo(lineView.rest[i]) > lineN) - return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true}; - } - - // Render a line into the hidden node display.externalMeasured. Used - // when measurement is needed for a line that's not in the viewport. - function updateExternalMeasurement(cm, line) { - line = visualLine(line); - var lineN = lineNo(line); - var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); - view.lineN = lineN; - var built = view.built = buildLineContent(cm, view); - view.text = built.pre; - removeChildrenAndAdd(cm.display.lineMeasure, built.pre); - return view; - } - - // Get a {top, bottom, left, right} box (in line-local coordinates) - // for a given character. - function measureChar(cm, line, ch, bias) { - return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); - } - - // Find a line view that corresponds to the given line number. - function findViewForLine(cm, lineN) { - if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) - return cm.display.view[findViewIndex(cm, lineN)]; - var ext = cm.display.externalMeasured; - if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) - return ext; - } - - // Measurement can be split in two steps, the set-up work that - // applies to the whole line, and the measurement of the actual - // character. Functions like coordsChar, that need to do a lot of - // measurements in a row, can thus ensure that the set-up work is - // only done once. - function prepareMeasureForLine(cm, line) { - var lineN = lineNo(line); - var view = findViewForLine(cm, lineN); - if (view && !view.text) { - view = null; - } else if (view && view.changes) { - updateLineForChanges(cm, view, lineN, getDimensions(cm)); - cm.curOp.forceUpdate = true; - } - if (!view) - view = updateExternalMeasurement(cm, line); - - var info = mapFromLineView(view, line, lineN); - return { - line: line, view: view, rect: null, - map: info.map, cache: info.cache, before: info.before, - hasHeights: false - }; - } - - // Given a prepared measurement object, measures the position of an - // actual character (or fetches it from the cache). - function measureCharPrepared(cm, prepared, ch, bias, varHeight) { - if (prepared.before) ch = -1; - var key = ch + (bias || ""), found; - if (prepared.cache.hasOwnProperty(key)) { - found = prepared.cache[key]; - } else { - if (!prepared.rect) - prepared.rect = prepared.view.text.getBoundingClientRect(); - if (!prepared.hasHeights) { - ensureLineHeights(cm, prepared.view, prepared.rect); - prepared.hasHeights = true; - } - found = measureCharInner(cm, prepared, ch, bias); - if (!found.bogus) prepared.cache[key] = found; - } - return {left: found.left, right: found.right, - top: varHeight ? found.rtop : found.top, - bottom: varHeight ? found.rbottom : found.bottom}; - } - - var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; - - function nodeAndOffsetInLineMap(map, ch, bias) { - var node, start, end, collapse; - // First, search the line map for the text node corresponding to, - // or closest to, the target character. - for (var i = 0; i < map.length; i += 3) { - var mStart = map[i], mEnd = map[i + 1]; - if (ch < mStart) { - start = 0; end = 1; - collapse = "left"; - } else if (ch < mEnd) { - start = ch - mStart; - end = start + 1; - } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { - end = mEnd - mStart; - start = end - 1; - if (ch >= mEnd) collapse = "right"; - } - if (start != null) { - node = map[i + 2]; - if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) - collapse = bias; - if (bias == "left" && start == 0) - while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { - node = map[(i -= 3) + 2]; - collapse = "left"; - } - if (bias == "right" && start == mEnd - mStart) - while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { - node = map[(i += 3) + 2]; - collapse = "right"; - } - break; - } - } - return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}; - } - - function measureCharInner(cm, prepared, ch, bias) { - var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); - var node = place.node, start = place.start, end = place.end, collapse = place.collapse; - - var rect; - if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. - for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned - while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start; - while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end; - if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) { - rect = node.parentNode.getBoundingClientRect(); - } else if (ie && cm.options.lineWrapping) { - var rects = range(node, start, end).getClientRects(); - if (rects.length) - rect = rects[bias == "right" ? rects.length - 1 : 0]; - else - rect = nullRect; - } else { - rect = range(node, start, end).getBoundingClientRect() || nullRect; - } - if (rect.left || rect.right || start == 0) break; - end = start; - start = start - 1; - collapse = "right"; - } - if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); - } else { // If it is a widget, simply get the box for the whole widget. - if (start > 0) collapse = bias = "right"; - var rects; - if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) - rect = rects[bias == "right" ? rects.length - 1 : 0]; - else - rect = node.getBoundingClientRect(); - } - if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { - var rSpan = node.parentNode.getClientRects()[0]; - if (rSpan) - rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; - else - rect = nullRect; - } - - var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; - var mid = (rtop + rbot) / 2; - var heights = prepared.view.measure.heights; - for (var i = 0; i < heights.length - 1; i++) - if (mid < heights[i]) break; - var top = i ? heights[i - 1] : 0, bot = heights[i]; - var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, - right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, - top: top, bottom: bot}; - if (!rect.left && !rect.right) result.bogus = true; - if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } - - return result; - } - - // Work around problem with bounding client rects on ranges being - // returned incorrectly when zoomed on IE10 and below. - function maybeUpdateRectForZooming(measure, rect) { - if (!window.screen || screen.logicalXDPI == null || - screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) - return rect; - var scaleX = screen.logicalXDPI / screen.deviceXDPI; - var scaleY = screen.logicalYDPI / screen.deviceYDPI; - return {left: rect.left * scaleX, right: rect.right * scaleX, - top: rect.top * scaleY, bottom: rect.bottom * scaleY}; - } - - function clearLineMeasurementCacheFor(lineView) { - if (lineView.measure) { - lineView.measure.cache = {}; - lineView.measure.heights = null; - if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) - lineView.measure.caches[i] = {}; - } - } - - function clearLineMeasurementCache(cm) { - cm.display.externalMeasure = null; - removeChildren(cm.display.lineMeasure); - for (var i = 0; i < cm.display.view.length; i++) - clearLineMeasurementCacheFor(cm.display.view[i]); - } - - function clearCaches(cm) { - clearLineMeasurementCache(cm); - cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; - if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; - cm.display.lineNumChars = null; - } - - function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } - function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } - - // Converts a {top, bottom, left, right} box from line-local - // coordinates into another coordinate system. Context may be one of - // "line", "div" (display.lineDiv), "local"/null (editor), "window", - // or "page". - function intoCoordSystem(cm, lineObj, rect, context) { - if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { - var size = widgetHeight(lineObj.widgets[i]); - rect.top += size; rect.bottom += size; - } - if (context == "line") return rect; - if (!context) context = "local"; - var yOff = heightAtLine(lineObj); - if (context == "local") yOff += paddingTop(cm.display); - else yOff -= cm.display.viewOffset; - if (context == "page" || context == "window") { - var lOff = cm.display.lineSpace.getBoundingClientRect(); - yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); - var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); - rect.left += xOff; rect.right += xOff; - } - rect.top += yOff; rect.bottom += yOff; - return rect; - } - - // Coverts a box from "div" coords to another coordinate system. - // Context may be "window", "page", "div", or "local"/null. - function fromCoordSystem(cm, coords, context) { - if (context == "div") return coords; - var left = coords.left, top = coords.top; - // First move into "page" coordinate system - if (context == "page") { - left -= pageScrollX(); - top -= pageScrollY(); - } else if (context == "local" || !context) { - var localBox = cm.display.sizer.getBoundingClientRect(); - left += localBox.left; - top += localBox.top; - } - - var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); - return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; - } - - function charCoords(cm, pos, context, lineObj, bias) { - if (!lineObj) lineObj = getLine(cm.doc, pos.line); - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context); - } - - // Returns a box for a given cursor position, which may have an - // 'other' property containing the position of the secondary cursor - // on a bidi boundary. - function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { - lineObj = lineObj || getLine(cm.doc, pos.line); - if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); - function get(ch, right) { - var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); - if (right) m.left = m.right; else m.right = m.left; - return intoCoordSystem(cm, lineObj, m, context); - } - function getBidi(ch, partPos) { - var part = order[partPos], right = part.level % 2; - if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { - part = order[--partPos]; - ch = bidiRight(part) - (part.level % 2 ? 0 : 1); - right = true; - } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { - part = order[++partPos]; - ch = bidiLeft(part) - part.level % 2; - right = false; - } - if (right && ch == part.to && ch > part.from) return get(ch - 1); - return get(ch, right); - } - var order = getOrder(lineObj), ch = pos.ch; - if (!order) return get(ch); - var partPos = getBidiPartAt(order, ch); - var val = getBidi(ch, partPos); - if (bidiOther != null) val.other = getBidi(ch, bidiOther); - return val; - } - - // Used to cheaply estimate the coordinates for a position. Used for - // intermediate scroll updates. - function estimateCoords(cm, pos) { - var left = 0, pos = clipPos(cm.doc, pos); - if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch; - var lineObj = getLine(cm.doc, pos.line); - var top = heightAtLine(lineObj) + paddingTop(cm.display); - return {left: left, right: left, top: top, bottom: top + lineObj.height}; - } - - // Positions returned by coordsChar contain some extra information. - // xRel is the relative x position of the input coordinates compared - // to the found position (so xRel > 0 means the coordinates are to - // the right of the character position, for example). When outside - // is true, that means the coordinates lie outside the line's - // vertical range. - function PosWithInfo(line, ch, outside, xRel) { - var pos = Pos(line, ch); - pos.xRel = xRel; - if (outside) pos.outside = true; - return pos; - } - - // Compute the character position closest to the given coordinates. - // Input must be lineSpace-local ("div" coordinate system). - function coordsChar(cm, x, y) { - var doc = cm.doc; - y += cm.display.viewOffset; - if (y < 0) return PosWithInfo(doc.first, 0, true, -1); - var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; - if (lineN > last) - return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); - if (x < 0) x = 0; - - var lineObj = getLine(doc, lineN); - for (;;) { - var found = coordsCharInner(cm, lineObj, lineN, x, y); - var merged = collapsedSpanAtEnd(lineObj); - var mergedPos = merged && merged.find(0, true); - if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) - lineN = lineNo(lineObj = mergedPos.to.line); - else - return found; - } - } - - function coordsCharInner(cm, lineObj, lineNo, x, y) { - var innerOff = y - heightAtLine(lineObj); - var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; - var preparedMeasure = prepareMeasureForLine(cm, lineObj); - - function getX(ch) { - var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure); - wrongLine = true; - if (innerOff > sp.bottom) return sp.left - adjust; - else if (innerOff < sp.top) return sp.left + adjust; - else wrongLine = false; - return sp.left; - } - - var bidi = getOrder(lineObj), dist = lineObj.text.length; - var from = lineLeft(lineObj), to = lineRight(lineObj); - var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; - - if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); - // Do a binary search between these bounds. - for (;;) { - if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { - var ch = x < fromX || x - fromX <= toX - x ? from : to; - var xDiff = x - (ch == from ? fromX : toX); - while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; - var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, - xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); - return pos; - } - var step = Math.ceil(dist / 2), middle = from + step; - if (bidi) { - middle = from; - for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); - } - var middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} - else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} - } - } - - var measureText; - // Compute the default text height. - function textHeight(display) { - if (display.cachedTextHeight != null) return display.cachedTextHeight; - if (measureText == null) { - measureText = elt("pre"); - // Measure a bunch of lines, for browsers that compute - // fractional heights. - for (var i = 0; i < 49; ++i) { - measureText.appendChild(document.createTextNode("x")); - measureText.appendChild(elt("br")); - } - measureText.appendChild(document.createTextNode("x")); - } - removeChildrenAndAdd(display.measure, measureText); - var height = measureText.offsetHeight / 50; - if (height > 3) display.cachedTextHeight = height; - removeChildren(display.measure); - return height || 1; - } - - // Compute the default character width. - function charWidth(display) { - if (display.cachedCharWidth != null) return display.cachedCharWidth; - var anchor = elt("span", "xxxxxxxxxx"); - var pre = elt("pre", [anchor]); - removeChildrenAndAdd(display.measure, pre); - var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; - if (width > 2) display.cachedCharWidth = width; - return width || 10; - } - - // OPERATIONS - - // Operations are used to wrap a series of changes to the editor - // state in such a way that each change won't have to update the - // cursor and display (which would be awkward, slow, and - // error-prone). Instead, display updates are batched and then all - // combined and executed at once. - - var operationGroup = null; - - var nextOpId = 0; - // Start a new operation. - function startOperation(cm) { - cm.curOp = { - cm: cm, - viewChanged: false, // Flag that indicates that lines might need to be redrawn - startHeight: cm.doc.height, // Used to detect need to update scrollbar - forceUpdate: false, // Used to force a redraw - updateInput: null, // Whether to reset the input textarea - typing: false, // Whether this reset should be careful to leave existing text (for compositing) - changeObjs: null, // Accumulated changes, for firing change events - cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on - cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already - selectionChanged: false, // Whether the selection needs to be redrawn - updateMaxLine: false, // Set when the widest line needs to be determined anew - scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet - scrollToPos: null, // Used to scroll to a specific position - focus: false, - id: ++nextOpId // Unique ID - }; - if (operationGroup) { - operationGroup.ops.push(cm.curOp); - } else { - cm.curOp.ownsGroup = operationGroup = { - ops: [cm.curOp], - delayedCallbacks: [] - }; - } - } - - function fireCallbacksForOps(group) { - // Calls delayed callbacks and cursorActivity handlers until no - // new ones appear - var callbacks = group.delayedCallbacks, i = 0; - do { - for (; i < callbacks.length; i++) - callbacks[i].call(null); - for (var j = 0; j < group.ops.length; j++) { - var op = group.ops[j]; - if (op.cursorActivityHandlers) - while (op.cursorActivityCalled < op.cursorActivityHandlers.length) - op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm); - } - } while (i < callbacks.length); - } - - // Finish an operation, updating the display and signalling delayed events - function endOperation(cm) { - var op = cm.curOp, group = op.ownsGroup; - if (!group) return; - - try { fireCallbacksForOps(group); } - finally { - operationGroup = null; - for (var i = 0; i < group.ops.length; i++) - group.ops[i].cm.curOp = null; - endOperations(group); - } - } - - // The DOM updates done when an operation finishes are batched so - // that the minimum number of relayouts are required. - function endOperations(group) { - var ops = group.ops; - for (var i = 0; i < ops.length; i++) // Read DOM - endOperation_R1(ops[i]); - for (var i = 0; i < ops.length; i++) // Write DOM (maybe) - endOperation_W1(ops[i]); - for (var i = 0; i < ops.length; i++) // Read DOM - endOperation_R2(ops[i]); - for (var i = 0; i < ops.length; i++) // Write DOM (maybe) - endOperation_W2(ops[i]); - for (var i = 0; i < ops.length; i++) // Read DOM - endOperation_finish(ops[i]); - } - - function endOperation_R1(op) { - var cm = op.cm, display = cm.display; - maybeClipScrollbars(cm); - if (op.updateMaxLine) findMaxLine(cm); - - op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || - op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || - op.scrollToPos.to.line >= display.viewTo) || - display.maxLineChanged && cm.options.lineWrapping; - op.update = op.mustUpdate && - new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); - } - - function endOperation_W1(op) { - op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); - } - - function endOperation_R2(op) { - var cm = op.cm, display = cm.display; - if (op.updatedDisplay) updateHeightsInViewport(cm); - - op.barMeasure = measureForScrollbars(cm); - - // If the max line changed since it was last measured, measure it, - // and ensure the document's width matches it. - // updateDisplay_W2 will use these properties to do the actual resizing - if (display.maxLineChanged && !cm.options.lineWrapping) { - op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; - cm.display.sizerWidth = op.adjustWidthTo; - op.barMeasure.scrollWidth = - Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); - op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); - } - - if (op.updatedDisplay || op.selectionChanged) - op.preparedSelection = display.input.prepareSelection(); - } - - function endOperation_W2(op) { - var cm = op.cm; - - if (op.adjustWidthTo != null) { - cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; - if (op.maxScrollLeft < cm.doc.scrollLeft) - setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); - cm.display.maxLineChanged = false; - } - - if (op.preparedSelection) - cm.display.input.showSelection(op.preparedSelection); - if (op.updatedDisplay) - setDocumentHeight(cm, op.barMeasure); - if (op.updatedDisplay || op.startHeight != cm.doc.height) - updateScrollbars(cm, op.barMeasure); - - if (op.selectionChanged) restartBlink(cm); - - if (cm.state.focused && op.updateInput) - cm.display.input.reset(op.typing); - if (op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus())) - ensureFocus(op.cm); - } - - function endOperation_finish(op) { - var cm = op.cm, display = cm.display, doc = cm.doc; - - if (op.updatedDisplay) postUpdateDisplay(cm, op.update); - - // Abort mouse wheel delta measurement, when scrolling explicitly - if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) - display.wheelStartX = display.wheelStartY = null; - - // Propagate the scroll position to the actual DOM scroller - if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { - doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); - display.scrollbars.setScrollTop(doc.scrollTop); - display.scroller.scrollTop = doc.scrollTop; - } - if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { - doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)); - display.scrollbars.setScrollLeft(doc.scrollLeft); - display.scroller.scrollLeft = doc.scrollLeft; - alignHorizontally(cm); - } - // If we need to scroll a specific position into view, do so. - if (op.scrollToPos) { - var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), - clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); - if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); - } - - // Fire events for markers that are hidden/unidden by editing or - // undoing - var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; - if (hidden) for (var i = 0; i < hidden.length; ++i) - if (!hidden[i].lines.length) signal(hidden[i], "hide"); - if (unhidden) for (var i = 0; i < unhidden.length; ++i) - if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); - - if (display.wrapper.offsetHeight) - doc.scrollTop = cm.display.scroller.scrollTop; - - // Fire change events, and delayed event handlers - if (op.changeObjs) - signal(cm, "changes", cm, op.changeObjs); - if (op.update) - op.update.finish(); - } - - // Run the given function in an operation - function runInOp(cm, f) { - if (cm.curOp) return f(); - startOperation(cm); - try { return f(); } - finally { endOperation(cm); } - } - // Wraps a function in an operation. Returns the wrapped function. - function operation(cm, f) { - return function() { - if (cm.curOp) return f.apply(cm, arguments); - startOperation(cm); - try { return f.apply(cm, arguments); } - finally { endOperation(cm); } - }; - } - // Used to add methods to editor and doc instances, wrapping them in - // operations. - function methodOp(f) { - return function() { - if (this.curOp) return f.apply(this, arguments); - startOperation(this); - try { return f.apply(this, arguments); } - finally { endOperation(this); } - }; - } - function docMethodOp(f) { - return function() { - var cm = this.cm; - if (!cm || cm.curOp) return f.apply(this, arguments); - startOperation(cm); - try { return f.apply(this, arguments); } - finally { endOperation(cm); } - }; - } - - // VIEW TRACKING - - // These objects are used to represent the visible (currently drawn) - // part of the document. A LineView may correspond to multiple - // logical lines, if those are connected by collapsed ranges. - function LineView(doc, line, lineN) { - // The starting line - this.line = line; - // Continuing lines, if any - this.rest = visualLineContinued(line); - // Number of logical lines in this visual line - this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; - this.node = this.text = null; - this.hidden = lineIsHidden(doc, line); - } - - // Create a range of LineView objects for the given lines. - function buildViewArray(cm, from, to) { - var array = [], nextPos; - for (var pos = from; pos < to; pos = nextPos) { - var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); - nextPos = pos + view.size; - array.push(view); - } - return array; - } - - // Updates the display.view data structure for a given change to the - // document. From and to are in pre-change coordinates. Lendiff is - // the amount of lines added or subtracted by the change. This is - // used for changes that span multiple lines, or change the way - // lines are divided into visual lines. regLineChange (below) - // registers single-line changes. - function regChange(cm, from, to, lendiff) { - if (from == null) from = cm.doc.first; - if (to == null) to = cm.doc.first + cm.doc.size; - if (!lendiff) lendiff = 0; - - var display = cm.display; - if (lendiff && to < display.viewTo && - (display.updateLineNumbers == null || display.updateLineNumbers > from)) - display.updateLineNumbers = from; - - cm.curOp.viewChanged = true; - - if (from >= display.viewTo) { // Change after - if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) - resetView(cm); - } else if (to <= display.viewFrom) { // Change before - if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { - resetView(cm); - } else { - display.viewFrom += lendiff; - display.viewTo += lendiff; - } - } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap - resetView(cm); - } else if (from <= display.viewFrom) { // Top overlap - var cut = viewCuttingPoint(cm, to, to + lendiff, 1); - if (cut) { - display.view = display.view.slice(cut.index); - display.viewFrom = cut.lineN; - display.viewTo += lendiff; - } else { - resetView(cm); - } - } else if (to >= display.viewTo) { // Bottom overlap - var cut = viewCuttingPoint(cm, from, from, -1); - if (cut) { - display.view = display.view.slice(0, cut.index); - display.viewTo = cut.lineN; - } else { - resetView(cm); - } - } else { // Gap in the middle - var cutTop = viewCuttingPoint(cm, from, from, -1); - var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); - if (cutTop && cutBot) { - display.view = display.view.slice(0, cutTop.index) - .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) - .concat(display.view.slice(cutBot.index)); - display.viewTo += lendiff; - } else { - resetView(cm); - } - } - - var ext = display.externalMeasured; - if (ext) { - if (to < ext.lineN) - ext.lineN += lendiff; - else if (from < ext.lineN + ext.size) - display.externalMeasured = null; - } - } - - // Register a change to a single line. Type must be one of "text", - // "gutter", "class", "widget" - function regLineChange(cm, line, type) { - cm.curOp.viewChanged = true; - var display = cm.display, ext = cm.display.externalMeasured; - if (ext && line >= ext.lineN && line < ext.lineN + ext.size) - display.externalMeasured = null; - - if (line < display.viewFrom || line >= display.viewTo) return; - var lineView = display.view[findViewIndex(cm, line)]; - if (lineView.node == null) return; - var arr = lineView.changes || (lineView.changes = []); - if (indexOf(arr, type) == -1) arr.push(type); - } - - // Clear the view. - function resetView(cm) { - cm.display.viewFrom = cm.display.viewTo = cm.doc.first; - cm.display.view = []; - cm.display.viewOffset = 0; - } - - // Find the view element corresponding to a given line. Return null - // when the line isn't visible. - function findViewIndex(cm, n) { - if (n >= cm.display.viewTo) return null; - n -= cm.display.viewFrom; - if (n < 0) return null; - var view = cm.display.view; - for (var i = 0; i < view.length; i++) { - n -= view[i].size; - if (n < 0) return i; - } - } - - function viewCuttingPoint(cm, oldN, newN, dir) { - var index = findViewIndex(cm, oldN), diff, view = cm.display.view; - if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) - return {index: index, lineN: newN}; - for (var i = 0, n = cm.display.viewFrom; i < index; i++) - n += view[i].size; - if (n != oldN) { - if (dir > 0) { - if (index == view.length - 1) return null; - diff = (n + view[index].size) - oldN; - index++; - } else { - diff = n - oldN; - } - oldN += diff; newN += diff; - } - while (visualLineNo(cm.doc, newN) != newN) { - if (index == (dir < 0 ? 0 : view.length - 1)) return null; - newN += dir * view[index - (dir < 0 ? 1 : 0)].size; - index += dir; - } - return {index: index, lineN: newN}; - } - - // Force the view to cover a given range, adding empty view element - // or clipping off existing ones as needed. - function adjustView(cm, from, to) { - var display = cm.display, view = display.view; - if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { - display.view = buildViewArray(cm, from, to); - display.viewFrom = from; - } else { - if (display.viewFrom > from) - display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); - else if (display.viewFrom < from) - display.view = display.view.slice(findViewIndex(cm, from)); - display.viewFrom = from; - if (display.viewTo < to) - display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); - else if (display.viewTo > to) - display.view = display.view.slice(0, findViewIndex(cm, to)); - } - display.viewTo = to; - } - - // Count the number of lines in the view whose DOM representation is - // out of date (or nonexistent). - function countDirtyView(cm) { - var view = cm.display.view, dirty = 0; - for (var i = 0; i < view.length; i++) { - var lineView = view[i]; - if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; - } - return dirty; - } - - // EVENT HANDLERS - - // Attach the necessary event handlers when initializing the editor - function registerEventHandlers(cm) { - var d = cm.display; - on(d.scroller, "mousedown", operation(cm, onMouseDown)); - // Older IE's will not fire a second mousedown for a double click - if (ie && ie_version < 11) - on(d.scroller, "dblclick", operation(cm, function(e) { - if (signalDOMEvent(cm, e)) return; - var pos = posFromMouse(cm, e); - if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; - e_preventDefault(e); - var word = cm.findWordAt(pos); - extendSelection(cm.doc, word.anchor, word.head); - })); - else - on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); - // Some browsers fire contextmenu *after* opening the menu, at - // which point we can't mess with it anymore. Context menu is - // handled in onMouseDown for these browsers. - if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); - - // Used to suppress mouse event handling when a touch happens - var touchFinished, prevTouch = {end: 0}; - function finishTouch() { - if (d.activeTouch) { - touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000); - prevTouch = d.activeTouch; - prevTouch.end = +new Date; - } - }; - function isMouseLikeTouchEvent(e) { - if (e.touches.length != 1) return false; - var touch = e.touches[0]; - return touch.radiusX <= 1 && touch.radiusY <= 1; - } - function farAway(touch, other) { - if (other.left == null) return true; - var dx = other.left - touch.left, dy = other.top - touch.top; - return dx * dx + dy * dy > 20 * 20; - } - on(d.scroller, "touchstart", function(e) { - if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) { - clearTimeout(touchFinished); - var now = +new Date; - d.activeTouch = {start: now, moved: false, - prev: now - prevTouch.end <= 300 ? prevTouch : null}; - if (e.touches.length == 1) { - d.activeTouch.left = e.touches[0].pageX; - d.activeTouch.top = e.touches[0].pageY; - } - } - }); - on(d.scroller, "touchmove", function() { - if (d.activeTouch) d.activeTouch.moved = true; - }); - on(d.scroller, "touchend", function(e) { - var touch = d.activeTouch; - if (touch && !eventInWidget(d, e) && touch.left != null && - !touch.moved && new Date - touch.start < 300) { - var pos = cm.coordsChar(d.activeTouch, "page"), range; - if (!touch.prev || farAway(touch, touch.prev)) // Single tap - range = new Range(pos, pos); - else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap - range = cm.findWordAt(pos); - else // Triple tap - range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); - cm.setSelection(range.anchor, range.head); - cm.focus(); - e_preventDefault(e); - } - finishTouch(); - }); - on(d.scroller, "touchcancel", finishTouch); - - // Sync scrolling between fake scrollbars and real scrollable - // area, ensure viewport is updated when scrolling. - on(d.scroller, "scroll", function() { - if (d.scroller.clientHeight) { - setScrollTop(cm, d.scroller.scrollTop); - setScrollLeft(cm, d.scroller.scrollLeft, true); - signal(cm, "scroll", cm); - } - }); - - // Listen to wheel events in order to try and update the viewport on time. - on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); - on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); - - // Prevent wrapper from ever scrolling - on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); - - d.dragFunctions = { - enter: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);}, - over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }}, - start: function(e){onDragStart(cm, e);}, - drop: operation(cm, onDrop), - leave: function() {clearDragCursor(cm);} - }; - - var inp = d.input.getField(); - on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); - on(inp, "keydown", operation(cm, onKeyDown)); - on(inp, "keypress", operation(cm, onKeyPress)); - on(inp, "focus", bind(onFocus, cm)); - on(inp, "blur", bind(onBlur, cm)); - } - - function dragDropChanged(cm, value, old) { - var wasOn = old && old != CodeMirror.Init; - if (!value != !wasOn) { - var funcs = cm.display.dragFunctions; - var toggle = value ? on : off; - toggle(cm.display.scroller, "dragstart", funcs.start); - toggle(cm.display.scroller, "dragenter", funcs.enter); - toggle(cm.display.scroller, "dragover", funcs.over); - toggle(cm.display.scroller, "dragleave", funcs.leave); - toggle(cm.display.scroller, "drop", funcs.drop); - } - } - - // Called when the window resizes - function onResize(cm) { - var d = cm.display; - if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) - return; - // Might be a text scaling operation, clear size caches. - d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; - d.scrollbarsClipped = false; - cm.setSize(); - } - - // MOUSE EVENTS - - // Return true when the given mouse event happened in a widget - function eventInWidget(display, e) { - for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { - if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || - (n.parentNode == display.sizer && n != display.mover)) - return true; - } - } - - // Given a mouse event, find the corresponding position. If liberal - // is false, it checks whether a gutter or scrollbar was clicked, - // and returns null if it was. forRect is used by rectangular - // selections, and tries to estimate a character position even for - // coordinates beyond the right of the text. - function posFromMouse(cm, e, liberal, forRect) { - var display = cm.display; - if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null; - - var x, y, space = display.lineSpace.getBoundingClientRect(); - // Fails unpredictably on IE[67] when mouse is dragged around quickly. - try { x = e.clientX - space.left; y = e.clientY - space.top; } - catch (e) { return null; } - var coords = coordsChar(cm, x, y), line; - if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { - var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; - coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); - } - return coords; - } - - // A mouse down can be a single click, double click, triple click, - // start of selection drag, start of text drag, new cursor - // (ctrl-click), rectangle drag (alt-drag), or xwin - // middle-click-paste. Or it might be a click on something we should - // not interfere with, such as a scrollbar or widget. - function onMouseDown(e) { - var cm = this, display = cm.display; - if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return; - display.shift = e.shiftKey; - - if (eventInWidget(display, e)) { - if (!webkit) { - // Briefly turn off draggability, to allow widgets to do - // normal dragging things. - display.scroller.draggable = false; - setTimeout(function(){display.scroller.draggable = true;}, 100); - } - return; - } - if (clickInGutter(cm, e)) return; - var start = posFromMouse(cm, e); - window.focus(); - - switch (e_button(e)) { - case 1: - // #3261: make sure, that we're not starting a second selection - if (cm.state.selectingText) - cm.state.selectingText(e); - else if (start) - leftButtonDown(cm, e, start); - else if (e_target(e) == display.scroller) - e_preventDefault(e); - break; - case 2: - if (webkit) cm.state.lastMiddleDown = +new Date; - if (start) extendSelection(cm.doc, start); - setTimeout(function() {display.input.focus();}, 20); - e_preventDefault(e); - break; - case 3: - if (captureRightClick) onContextMenu(cm, e); - else delayBlurEvent(cm); - break; - } - } - - var lastClick, lastDoubleClick; - function leftButtonDown(cm, e, start) { - if (ie) setTimeout(bind(ensureFocus, cm), 0); - else cm.curOp.focus = activeElt(); - - var now = +new Date, type; - if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { - type = "triple"; - } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { - type = "double"; - lastDoubleClick = {time: now, pos: start}; - } else { - type = "single"; - lastClick = {time: now, pos: start}; - } - - var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; - if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() && - type == "single" && (contained = sel.contains(start)) > -1 && - (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) && - (cmp(contained.to(), start) > 0 || start.xRel < 0)) - leftButtonStartDrag(cm, e, start, modifier); - else - leftButtonSelect(cm, e, start, type, modifier); - } - - // Start a text drag. When it ends, see if any dragging actually - // happen, and treat as a click if it didn't. - function leftButtonStartDrag(cm, e, start, modifier) { - var display = cm.display, startTime = +new Date; - var dragEnd = operation(cm, function(e2) { - if (webkit) display.scroller.draggable = false; - cm.state.draggingText = false; - off(document, "mouseup", dragEnd); - off(display.scroller, "drop", dragEnd); - if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { - e_preventDefault(e2); - if (!modifier && +new Date - 200 < startTime) - extendSelection(cm.doc, start); - // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) - if (webkit || ie && ie_version == 9) - setTimeout(function() {document.body.focus(); display.input.focus();}, 20); - else - display.input.focus(); - } - }); - // Let the drag handler handle this. - if (webkit) display.scroller.draggable = true; - cm.state.draggingText = dragEnd; - // IE's approach to draggable - if (display.scroller.dragDrop) display.scroller.dragDrop(); - on(document, "mouseup", dragEnd); - on(display.scroller, "drop", dragEnd); - } - - // Normal selection, as opposed to text dragging. - function leftButtonSelect(cm, e, start, type, addNew) { - var display = cm.display, doc = cm.doc; - e_preventDefault(e); - - var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; - if (addNew && !e.shiftKey) { - ourIndex = doc.sel.contains(start); - if (ourIndex > -1) - ourRange = ranges[ourIndex]; - else - ourRange = new Range(start, start); - } else { - ourRange = doc.sel.primary(); - ourIndex = doc.sel.primIndex; - } - - if (e.altKey) { - type = "rect"; - if (!addNew) ourRange = new Range(start, start); - start = posFromMouse(cm, e, true, true); - ourIndex = -1; - } else if (type == "double") { - var word = cm.findWordAt(start); - if (cm.display.shift || doc.extend) - ourRange = extendRange(doc, ourRange, word.anchor, word.head); - else - ourRange = word; - } else if (type == "triple") { - var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))); - if (cm.display.shift || doc.extend) - ourRange = extendRange(doc, ourRange, line.anchor, line.head); - else - ourRange = line; - } else { - ourRange = extendRange(doc, ourRange, start); - } - - if (!addNew) { - ourIndex = 0; - setSelection(doc, new Selection([ourRange], 0), sel_mouse); - startSel = doc.sel; - } else if (ourIndex == -1) { - ourIndex = ranges.length; - setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), - {scroll: false, origin: "*mouse"}); - } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { - setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0), - {scroll: false, origin: "*mouse"}); - startSel = doc.sel; - } else { - replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); - } - - var lastPos = start; - function extendTo(pos) { - if (cmp(lastPos, pos) == 0) return; - lastPos = pos; - - if (type == "rect") { - var ranges = [], tabSize = cm.options.tabSize; - var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); - var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); - var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); - for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); - line <= end; line++) { - var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); - if (left == right) - ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); - else if (text.length > leftPos) - ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); - } - if (!ranges.length) ranges.push(new Range(start, start)); - setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), - {origin: "*mouse", scroll: false}); - cm.scrollIntoView(pos); - } else { - var oldRange = ourRange; - var anchor = oldRange.anchor, head = pos; - if (type != "single") { - if (type == "double") - var range = cm.findWordAt(pos); - else - var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))); - if (cmp(range.anchor, anchor) > 0) { - head = range.head; - anchor = minPos(oldRange.from(), range.anchor); - } else { - head = range.anchor; - anchor = maxPos(oldRange.to(), range.head); - } - } - var ranges = startSel.ranges.slice(0); - ranges[ourIndex] = new Range(clipPos(doc, anchor), head); - setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse); - } - } - - var editorSize = display.wrapper.getBoundingClientRect(); - // Used to ensure timeout re-tries don't fire when another extend - // happened in the meantime (clearTimeout isn't reliable -- at - // least on Chrome, the timeouts still happen even when cleared, - // if the clear happens after their scheduled firing time). - var counter = 0; - - function extend(e) { - var curCount = ++counter; - var cur = posFromMouse(cm, e, true, type == "rect"); - if (!cur) return; - if (cmp(cur, lastPos) != 0) { - cm.curOp.focus = activeElt(); - extendTo(cur); - var visible = visibleLines(display, doc); - if (cur.line >= visible.to || cur.line < visible.from) - setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); - } else { - var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; - if (outside) setTimeout(operation(cm, function() { - if (counter != curCount) return; - display.scroller.scrollTop += outside; - extend(e); - }), 50); - } - } - - function done(e) { - cm.state.selectingText = false; - counter = Infinity; - e_preventDefault(e); - display.input.focus(); - off(document, "mousemove", move); - off(document, "mouseup", up); - doc.history.lastSelOrigin = null; - } - - var move = operation(cm, function(e) { - if (!e_button(e)) done(e); - else extend(e); - }); - var up = operation(cm, done); - cm.state.selectingText = up; - on(document, "mousemove", move); - on(document, "mouseup", up); - } - - // Determines whether an event happened in the gutter, and fires the - // handlers for the corresponding event. - function gutterEvent(cm, e, type, prevent) { - try { var mX = e.clientX, mY = e.clientY; } - catch(e) { return false; } - if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false; - if (prevent) e_preventDefault(e); - - var display = cm.display; - var lineBox = display.lineDiv.getBoundingClientRect(); - - if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); - mY -= lineBox.top - display.viewOffset; - - for (var i = 0; i < cm.options.gutters.length; ++i) { - var g = display.gutters.childNodes[i]; - if (g && g.getBoundingClientRect().right >= mX) { - var line = lineAtHeight(cm.doc, mY); - var gutter = cm.options.gutters[i]; - signal(cm, type, cm, line, gutter, e); - return e_defaultPrevented(e); - } - } - } - - function clickInGutter(cm, e) { - return gutterEvent(cm, e, "gutterClick", true); - } - - // Kludge to work around strange IE behavior where it'll sometimes - // re-fire a series of drag-related events right after the drop (#1551) - var lastDrop = 0; - - function onDrop(e) { - var cm = this; - clearDragCursor(cm); - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) - return; - e_preventDefault(e); - if (ie) lastDrop = +new Date; - var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; - if (!pos || cm.isReadOnly()) return; - // Might be a file drop, in which case we simply extract the text - // and insert it. - if (files && files.length && window.FileReader && window.File) { - var n = files.length, text = Array(n), read = 0; - var loadFile = function(file, i) { - if (cm.options.allowDropFileTypes && - indexOf(cm.options.allowDropFileTypes, file.type) == -1) - return; - - var reader = new FileReader; - reader.onload = operation(cm, function() { - var content = reader.result; - if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = ""; - text[i] = content; - if (++read == n) { - pos = clipPos(cm.doc, pos); - var change = {from: pos, to: pos, - text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), - origin: "paste"}; - makeChange(cm.doc, change); - setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); - } - }); - reader.readAsText(file); - }; - for (var i = 0; i < n; ++i) loadFile(files[i], i); - } else { // Normal drop - // Don't do a replace if the drop happened inside of the selected text. - if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { - cm.state.draggingText(e); - // Ensure the editor is re-focused - setTimeout(function() {cm.display.input.focus();}, 20); - return; - } - try { - var text = e.dataTransfer.getData("Text"); - if (text) { - if (cm.state.draggingText && !(mac ? e.altKey : e.ctrlKey)) - var selected = cm.listSelections(); - setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); - if (selected) for (var i = 0; i < selected.length; ++i) - replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); - cm.replaceSelection(text, "around", "paste"); - cm.display.input.focus(); - } - } - catch(e){} - } - } - - function onDragStart(cm, e) { - if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; - - e.dataTransfer.setData("Text", cm.getSelection()); - - // Use dummy image instead of default browsers image. - // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. - if (e.dataTransfer.setDragImage && !safari) { - var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); - img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; - if (presto) { - img.width = img.height = 1; - cm.display.wrapper.appendChild(img); - // Force a relayout, or Opera won't use our image for some obscure reason - img._top = img.offsetTop; - } - e.dataTransfer.setDragImage(img, 0, 0); - if (presto) img.parentNode.removeChild(img); - } - } - - function onDragOver(cm, e) { - var pos = posFromMouse(cm, e); - if (!pos) return; - var frag = document.createDocumentFragment(); - drawSelectionCursor(cm, pos, frag); - if (!cm.display.dragCursor) { - cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors"); - cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv); - } - removeChildrenAndAdd(cm.display.dragCursor, frag); - } - - function clearDragCursor(cm) { - if (cm.display.dragCursor) { - cm.display.lineSpace.removeChild(cm.display.dragCursor); - cm.display.dragCursor = null; - } - } - - // SCROLL EVENTS - - // Sync the scrollable area and scrollbars, ensure the viewport - // covers the visible area. - function setScrollTop(cm, val) { - if (Math.abs(cm.doc.scrollTop - val) < 2) return; - cm.doc.scrollTop = val; - if (!gecko) updateDisplaySimple(cm, {top: val}); - if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; - cm.display.scrollbars.setScrollTop(val); - if (gecko) updateDisplaySimple(cm); - startWorker(cm, 100); - } - // Sync scroller and scrollbar, ensure the gutter elements are - // aligned. - function setScrollLeft(cm, val, isScroller) { - if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; - val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); - cm.doc.scrollLeft = val; - alignHorizontally(cm); - if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; - cm.display.scrollbars.setScrollLeft(val); - } - - // Since the delta values reported on mouse wheel events are - // unstandardized between browsers and even browser versions, and - // generally horribly unpredictable, this code starts by measuring - // the scroll effect that the first few mouse wheel events have, - // and, from that, detects the way it can convert deltas to pixel - // offsets afterwards. - // - // The reason we want to know the amount a wheel event will scroll - // is that it gives us a chance to update the display before the - // actual scrolling happens, reducing flickering. - - var wheelSamples = 0, wheelPixelsPerUnit = null; - // Fill in a browser-detected starting value on browsers where we - // know one. These don't have to be accurate -- the result of them - // being wrong would just be a slight flicker on the first wheel - // scroll (if it is large enough). - if (ie) wheelPixelsPerUnit = -.53; - else if (gecko) wheelPixelsPerUnit = 15; - else if (chrome) wheelPixelsPerUnit = -.7; - else if (safari) wheelPixelsPerUnit = -1/3; - - var wheelEventDelta = function(e) { - var dx = e.wheelDeltaX, dy = e.wheelDeltaY; - if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; - if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; - else if (dy == null) dy = e.wheelDelta; - return {x: dx, y: dy}; - }; - CodeMirror.wheelEventPixels = function(e) { - var delta = wheelEventDelta(e); - delta.x *= wheelPixelsPerUnit; - delta.y *= wheelPixelsPerUnit; - return delta; - }; - - function onScrollWheel(cm, e) { - var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; - - var display = cm.display, scroll = display.scroller; - // Quit if there's nothing to scroll here - var canScrollX = scroll.scrollWidth > scroll.clientWidth; - var canScrollY = scroll.scrollHeight > scroll.clientHeight; - if (!(dx && canScrollX || dy && canScrollY)) return; - - // Webkit browsers on OS X abort momentum scrolls when the target - // of the scroll event is removed from the scrollable element. - // This hack (see related code in patchDisplay) makes sure the - // element is kept around. - if (dy && mac && webkit) { - outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { - for (var i = 0; i < view.length; i++) { - if (view[i].node == cur) { - cm.display.currentWheelTarget = cur; - break outer; - } - } - } - } - - // On some browsers, horizontal scrolling will cause redraws to - // happen before the gutter has been realigned, causing it to - // wriggle around in a most unseemly way. When we have an - // estimated pixels/delta value, we just handle horizontal - // scrolling entirely here. It'll be slightly off from native, but - // better than glitching out. - if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { - if (dy && canScrollY) - setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); - setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); - // Only prevent default scrolling if vertical scrolling is - // actually possible. Otherwise, it causes vertical scroll - // jitter on OSX trackpads when deltaX is small and deltaY - // is large (issue #3579) - if (!dy || (dy && canScrollY)) - e_preventDefault(e); - display.wheelStartX = null; // Abort measurement, if in progress - return; - } - - // 'Project' the visible viewport to cover the area that is being - // scrolled into view (if we know enough to estimate it). - if (dy && wheelPixelsPerUnit != null) { - var pixels = dy * wheelPixelsPerUnit; - var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; - if (pixels < 0) top = Math.max(0, top + pixels - 50); - else bot = Math.min(cm.doc.height, bot + pixels + 50); - updateDisplaySimple(cm, {top: top, bottom: bot}); - } - - if (wheelSamples < 20) { - if (display.wheelStartX == null) { - display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; - display.wheelDX = dx; display.wheelDY = dy; - setTimeout(function() { - if (display.wheelStartX == null) return; - var movedX = scroll.scrollLeft - display.wheelStartX; - var movedY = scroll.scrollTop - display.wheelStartY; - var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || - (movedX && display.wheelDX && movedX / display.wheelDX); - display.wheelStartX = display.wheelStartY = null; - if (!sample) return; - wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); - ++wheelSamples; - }, 200); - } else { - display.wheelDX += dx; display.wheelDY += dy; - } - } - } - - // KEY EVENTS - - // Run a handler that was bound to a key. - function doHandleBinding(cm, bound, dropShift) { - if (typeof bound == "string") { - bound = commands[bound]; - if (!bound) return false; - } - // Ensure previous input has been read, so that the handler sees a - // consistent view of the document - cm.display.input.ensurePolled(); - var prevShift = cm.display.shift, done = false; - try { - if (cm.isReadOnly()) cm.state.suppressEdits = true; - if (dropShift) cm.display.shift = false; - done = bound(cm) != Pass; - } finally { - cm.display.shift = prevShift; - cm.state.suppressEdits = false; - } - return done; - } - - function lookupKeyForEditor(cm, name, handle) { - for (var i = 0; i < cm.state.keyMaps.length; i++) { - var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); - if (result) return result; - } - return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) - || lookupKey(name, cm.options.keyMap, handle, cm); - } - - var stopSeq = new Delayed; - function dispatchKey(cm, name, e, handle) { - var seq = cm.state.keySeq; - if (seq) { - if (isModifierKey(name)) return "handled"; - stopSeq.set(50, function() { - if (cm.state.keySeq == seq) { - cm.state.keySeq = null; - cm.display.input.reset(); - } - }); - name = seq + " " + name; - } - var result = lookupKeyForEditor(cm, name, handle); - - if (result == "multi") - cm.state.keySeq = name; - if (result == "handled") - signalLater(cm, "keyHandled", cm, name, e); - - if (result == "handled" || result == "multi") { - e_preventDefault(e); - restartBlink(cm); - } - - if (seq && !result && /\'$/.test(name)) { - e_preventDefault(e); - return true; - } - return !!result; - } - - // Handle a key from the keydown event. - function handleKeyBinding(cm, e) { - var name = keyName(e, true); - if (!name) return false; - - if (e.shiftKey && !cm.state.keySeq) { - // First try to resolve full name (including 'Shift-'). Failing - // that, see if there is a cursor-motion command (starting with - // 'go') bound to the keyname without 'Shift-'. - return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBinding(cm, b, true);}) - || dispatchKey(cm, name, e, function(b) { - if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) - return doHandleBinding(cm, b); - }); - } else { - return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); }); - } - } - - // Handle a key from the keypress event - function handleCharBinding(cm, e, ch) { - return dispatchKey(cm, "'" + ch + "'", e, - function(b) { return doHandleBinding(cm, b, true); }); - } - - var lastStoppedKey = null; - function onKeyDown(e) { - var cm = this; - cm.curOp.focus = activeElt(); - if (signalDOMEvent(cm, e)) return; - // IE does strange things with escape. - if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; - var code = e.keyCode; - cm.display.shift = code == 16 || e.shiftKey; - var handled = handleKeyBinding(cm, e); - if (presto) { - lastStoppedKey = handled ? code : null; - // Opera has no cut event... we try to at least catch the key combo - if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) - cm.replaceSelection("", null, "cut"); - } - - // Turn mouse into crosshair when Alt is held on Mac. - if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) - showCrossHair(cm); - } - - function showCrossHair(cm) { - var lineDiv = cm.display.lineDiv; - addClass(lineDiv, "CodeMirror-crosshair"); - - function up(e) { - if (e.keyCode == 18 || !e.altKey) { - rmClass(lineDiv, "CodeMirror-crosshair"); - off(document, "keyup", up); - off(document, "mouseover", up); - } - } - on(document, "keyup", up); - on(document, "mouseover", up); - } - - function onKeyUp(e) { - if (e.keyCode == 16) this.doc.sel.shift = false; - signalDOMEvent(this, e); - } - - function onKeyPress(e) { - var cm = this; - if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; - var keyCode = e.keyCode, charCode = e.charCode; - if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} - if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return; - var ch = String.fromCharCode(charCode == null ? keyCode : charCode); - if (handleCharBinding(cm, e, ch)) return; - cm.display.input.onKeyPress(e); - } - - // FOCUS/BLUR EVENTS - - function delayBlurEvent(cm) { - cm.state.delayingBlurEvent = true; - setTimeout(function() { - if (cm.state.delayingBlurEvent) { - cm.state.delayingBlurEvent = false; - onBlur(cm); - } - }, 100); - } - - function onFocus(cm) { - if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; - - if (cm.options.readOnly == "nocursor") return; - if (!cm.state.focused) { - signal(cm, "focus", cm); - cm.state.focused = true; - addClass(cm.display.wrapper, "CodeMirror-focused"); - // This test prevents this from firing when a context - // menu is closed (since the input reset would kill the - // select-all detection hack) - if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { - cm.display.input.reset(); - if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20); // Issue #1730 - } - cm.display.input.receivedFocus(); - } - restartBlink(cm); - } - function onBlur(cm) { - if (cm.state.delayingBlurEvent) return; - - if (cm.state.focused) { - signal(cm, "blur", cm); - cm.state.focused = false; - rmClass(cm.display.wrapper, "CodeMirror-focused"); - } - clearInterval(cm.display.blinker); - setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150); - } - - // CONTEXT MENU HANDLING - - // To make the context menu work, we need to briefly unhide the - // textarea (making it as unobtrusive as possible) to let the - // right-click take effect on it. - function onContextMenu(cm, e) { - if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return; - if (signalDOMEvent(cm, e, "contextmenu")) return; - cm.display.input.onContextMenu(e); - } - - function contextMenuInGutter(cm, e) { - if (!hasHandler(cm, "gutterContextMenu")) return false; - return gutterEvent(cm, e, "gutterContextMenu", false); - } - - // UPDATING - - // Compute the position of the end of a change (its 'to' property - // refers to the pre-change end). - var changeEnd = CodeMirror.changeEnd = function(change) { - if (!change.text) return change.to; - return Pos(change.from.line + change.text.length - 1, - lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); - }; - - // Adjust a position to refer to the post-change position of the - // same text, or the end of the change if the change covers it. - function adjustForChange(pos, change) { - if (cmp(pos, change.from) < 0) return pos; - if (cmp(pos, change.to) <= 0) return changeEnd(change); - - var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; - if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch; - return Pos(line, ch); - } - - function computeSelAfterChange(doc, change) { - var out = []; - for (var i = 0; i < doc.sel.ranges.length; i++) { - var range = doc.sel.ranges[i]; - out.push(new Range(adjustForChange(range.anchor, change), - adjustForChange(range.head, change))); - } - return normalizeSelection(out, doc.sel.primIndex); - } - - function offsetPos(pos, old, nw) { - if (pos.line == old.line) - return Pos(nw.line, pos.ch - old.ch + nw.ch); - else - return Pos(nw.line + (pos.line - old.line), pos.ch); - } - - // Used by replaceSelections to allow moving the selection to the - // start or around the replaced test. Hint may be "start" or "around". - function computeReplacedSel(doc, changes, hint) { - var out = []; - var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; - for (var i = 0; i < changes.length; i++) { - var change = changes[i]; - var from = offsetPos(change.from, oldPrev, newPrev); - var to = offsetPos(changeEnd(change), oldPrev, newPrev); - oldPrev = change.to; - newPrev = to; - if (hint == "around") { - var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; - out[i] = new Range(inv ? to : from, inv ? from : to); - } else { - out[i] = new Range(from, from); - } - } - return new Selection(out, doc.sel.primIndex); - } - - // Allow "beforeChange" event handlers to influence a change - function filterChange(doc, change, update) { - var obj = { - canceled: false, - from: change.from, - to: change.to, - text: change.text, - origin: change.origin, - cancel: function() { this.canceled = true; } - }; - if (update) obj.update = function(from, to, text, origin) { - if (from) this.from = clipPos(doc, from); - if (to) this.to = clipPos(doc, to); - if (text) this.text = text; - if (origin !== undefined) this.origin = origin; - }; - signal(doc, "beforeChange", doc, obj); - if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); - - if (obj.canceled) return null; - return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; - } - - // Apply a change to a document, and add it to the document's - // history, and propagating it to all linked documents. - function makeChange(doc, change, ignoreReadOnly) { - if (doc.cm) { - if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly); - if (doc.cm.state.suppressEdits) return; - } - - if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { - change = filterChange(doc, change, true); - if (!change) return; - } - - // Possibly split or suppress the update based on the presence - // of read-only spans in its range. - var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); - if (split) { - for (var i = split.length - 1; i >= 0; --i) - makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}); - } else { - makeChangeInner(doc, change); - } - } - - function makeChangeInner(doc, change) { - if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return; - var selAfter = computeSelAfterChange(doc, change); - addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); - - makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); - var rebased = []; - - linkedDocs(doc, function(doc, sharedHist) { - if (!sharedHist && indexOf(rebased, doc.history) == -1) { - rebaseHist(doc.history, change); - rebased.push(doc.history); - } - makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); - }); - } - - // Revert a change stored in a document's history. - function makeChangeFromHistory(doc, type, allowSelectionOnly) { - if (doc.cm && doc.cm.state.suppressEdits) return; - - var hist = doc.history, event, selAfter = doc.sel; - var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; - - // Verify that there is a useable event (so that ctrl-z won't - // needlessly clear selection events) - for (var i = 0; i < source.length; i++) { - event = source[i]; - if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) - break; - } - if (i == source.length) return; - hist.lastOrigin = hist.lastSelOrigin = null; - - for (;;) { - event = source.pop(); - if (event.ranges) { - pushSelectionToHistory(event, dest); - if (allowSelectionOnly && !event.equals(doc.sel)) { - setSelection(doc, event, {clearRedo: false}); - return; - } - selAfter = event; - } - else break; - } - - // Build up a reverse change object to add to the opposite history - // stack (redo when undoing, and vice versa). - var antiChanges = []; - pushSelectionToHistory(selAfter, dest); - dest.push({changes: antiChanges, generation: hist.generation}); - hist.generation = event.generation || ++hist.maxGeneration; - - var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); - - for (var i = event.changes.length - 1; i >= 0; --i) { - var change = event.changes[i]; - change.origin = type; - if (filter && !filterChange(doc, change, false)) { - source.length = 0; - return; - } - - antiChanges.push(historyChangeFromChange(doc, change)); - - var after = i ? computeSelAfterChange(doc, change) : lst(source); - makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); - if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); - var rebased = []; - - // Propagate to the linked documents - linkedDocs(doc, function(doc, sharedHist) { - if (!sharedHist && indexOf(rebased, doc.history) == -1) { - rebaseHist(doc.history, change); - rebased.push(doc.history); - } - makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); - }); - } - } - - // Sub-views need their line numbers shifted when text is added - // above or below them in the parent document. - function shiftDoc(doc, distance) { - if (distance == 0) return; - doc.first += distance; - doc.sel = new Selection(map(doc.sel.ranges, function(range) { - return new Range(Pos(range.anchor.line + distance, range.anchor.ch), - Pos(range.head.line + distance, range.head.ch)); - }), doc.sel.primIndex); - if (doc.cm) { - regChange(doc.cm, doc.first, doc.first - distance, distance); - for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) - regLineChange(doc.cm, l, "gutter"); - } - } - - // More lower-level change function, handling only a single document - // (not linked ones). - function makeChangeSingleDoc(doc, change, selAfter, spans) { - if (doc.cm && !doc.cm.curOp) - return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); - - if (change.to.line < doc.first) { - shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); - return; - } - if (change.from.line > doc.lastLine()) return; - - // Clip the change to the size of this doc - if (change.from.line < doc.first) { - var shift = change.text.length - 1 - (doc.first - change.from.line); - shiftDoc(doc, shift); - change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), - text: [lst(change.text)], origin: change.origin}; - } - var last = doc.lastLine(); - if (change.to.line > last) { - change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), - text: [change.text[0]], origin: change.origin}; - } - - change.removed = getBetween(doc, change.from, change.to); - - if (!selAfter) selAfter = computeSelAfterChange(doc, change); - if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); - else updateDoc(doc, change, spans); - setSelectionNoUndo(doc, selAfter, sel_dontScroll); - } - - // Handle the interaction of a change to a document with the editor - // that this document is part of. - function makeChangeSingleDocInEditor(cm, change, spans) { - var doc = cm.doc, display = cm.display, from = change.from, to = change.to; - - var recomputeMaxLength = false, checkWidthStart = from.line; - if (!cm.options.lineWrapping) { - checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); - doc.iter(checkWidthStart, to.line + 1, function(line) { - if (line == display.maxLine) { - recomputeMaxLength = true; - return true; - } - }); - } - - if (doc.sel.contains(change.from, change.to) > -1) - signalCursorActivity(cm); - - updateDoc(doc, change, spans, estimateHeight(cm)); - - if (!cm.options.lineWrapping) { - doc.iter(checkWidthStart, from.line + change.text.length, function(line) { - var len = lineLength(line); - if (len > display.maxLineLength) { - display.maxLine = line; - display.maxLineLength = len; - display.maxLineChanged = true; - recomputeMaxLength = false; - } - }); - if (recomputeMaxLength) cm.curOp.updateMaxLine = true; - } - - // Adjust frontier, schedule worker - doc.frontier = Math.min(doc.frontier, from.line); - startWorker(cm, 400); - - var lendiff = change.text.length - (to.line - from.line) - 1; - // Remember that these lines changed, for updating the display - if (change.full) - regChange(cm); - else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) - regLineChange(cm, from.line, "text"); - else - regChange(cm, from.line, to.line + 1, lendiff); - - var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); - if (changeHandler || changesHandler) { - var obj = { - from: from, to: to, - text: change.text, - removed: change.removed, - origin: change.origin - }; - if (changeHandler) signalLater(cm, "change", cm, obj); - if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); - } - cm.display.selForContextMenu = null; - } - - function replaceRange(doc, code, from, to, origin) { - if (!to) to = from; - if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } - if (typeof code == "string") code = doc.splitLines(code); - makeChange(doc, {from: from, to: to, text: code, origin: origin}); - } - - // SCROLLING THINGS INTO VIEW - - // If an editor sits on the top or bottom of the window, partially - // scrolled out of view, this ensures that the cursor is visible. - function maybeScrollWindow(cm, coords) { - if (signalDOMEvent(cm, "scrollCursorIntoView")) return; - - var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; - if (coords.top + box.top < 0) doScroll = true; - else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; - if (doScroll != null && !phantom) { - var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + - (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + - (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " + - coords.left + "px; width: 2px;"); - cm.display.lineSpace.appendChild(scrollNode); - scrollNode.scrollIntoView(doScroll); - cm.display.lineSpace.removeChild(scrollNode); - } - } - - // Scroll a given position into view (immediately), verifying that - // it actually became visible (as line heights are accurately - // measured, the position of something may 'drift' during drawing). - function scrollPosIntoView(cm, pos, end, margin) { - if (margin == null) margin = 0; - for (var limit = 0; limit < 5; limit++) { - var changed = false, coords = cursorCoords(cm, pos); - var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); - var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), - Math.min(coords.top, endCoords.top) - margin, - Math.max(coords.left, endCoords.left), - Math.max(coords.bottom, endCoords.bottom) + margin); - var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; - if (scrollPos.scrollTop != null) { - setScrollTop(cm, scrollPos.scrollTop); - if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; - } - if (scrollPos.scrollLeft != null) { - setScrollLeft(cm, scrollPos.scrollLeft); - if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; - } - if (!changed) break; - } - return coords; - } - - // Scroll a given set of coordinates into view (immediately). - function scrollIntoView(cm, x1, y1, x2, y2) { - var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); - if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); - if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); - } - - // Calculate a new scroll position needed to scroll the given - // rectangle into view. Returns an object with scrollTop and - // scrollLeft properties. When these are undefined, the - // vertical/horizontal position does not need to be adjusted. - function calculateScrollPos(cm, x1, y1, x2, y2) { - var display = cm.display, snapMargin = textHeight(cm.display); - if (y1 < 0) y1 = 0; - var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; - var screen = displayHeight(cm), result = {}; - if (y2 - y1 > screen) y2 = y1 + screen; - var docBottom = cm.doc.height + paddingVert(display); - var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; - if (y1 < screentop) { - result.scrollTop = atTop ? 0 : y1; - } else if (y2 > screentop + screen) { - var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); - if (newTop != screentop) result.scrollTop = newTop; - } - - var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; - var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); - var tooWide = x2 - x1 > screenw; - if (tooWide) x2 = x1 + screenw; - if (x1 < 10) - result.scrollLeft = 0; - else if (x1 < screenleft) - result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); - else if (x2 > screenw + screenleft - 3) - result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; - return result; - } - - // Store a relative adjustment to the scroll position in the current - // operation (to be applied when the operation finishes). - function addToScrollPos(cm, left, top) { - if (left != null || top != null) resolveScrollToPos(cm); - if (left != null) - cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left; - if (top != null) - cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; - } - - // Make sure that at the end of the operation the current cursor is - // shown. - function ensureCursorVisible(cm) { - resolveScrollToPos(cm); - var cur = cm.getCursor(), from = cur, to = cur; - if (!cm.options.lineWrapping) { - from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur; - to = Pos(cur.line, cur.ch + 1); - } - cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true}; - } - - // When an operation has its scrollToPos property set, and another - // scroll action is applied before the end of the operation, this - // 'simulates' scrolling that position into view in a cheap way, so - // that the effect of intermediate scroll commands is not ignored. - function resolveScrollToPos(cm) { - var range = cm.curOp.scrollToPos; - if (range) { - cm.curOp.scrollToPos = null; - var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); - var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), - Math.min(from.top, to.top) - range.margin, - Math.max(from.right, to.right), - Math.max(from.bottom, to.bottom) + range.margin); - cm.scrollTo(sPos.scrollLeft, sPos.scrollTop); - } - } - - // API UTILITIES - - // Indent the given line. The how parameter can be "smart", - // "add"/null, "subtract", or "prev". When aggressive is false - // (typically set to true for forced single-line indents), empty - // lines are not indented, and places where the mode returns Pass - // are left alone. - function indentLine(cm, n, how, aggressive) { - var doc = cm.doc, state; - if (how == null) how = "add"; - if (how == "smart") { - // Fall back to "prev" when the mode doesn't have an indentation - // method. - if (!doc.mode.indent) how = "prev"; - else state = getStateBefore(cm, n); - } - - var tabSize = cm.options.tabSize; - var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); - if (line.stateAfter) line.stateAfter = null; - var curSpaceString = line.text.match(/^\s*/)[0], indentation; - if (!aggressive && !/\S/.test(line.text)) { - indentation = 0; - how = "not"; - } else if (how == "smart") { - indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); - if (indentation == Pass || indentation > 150) { - if (!aggressive) return; - how = "prev"; - } - } - if (how == "prev") { - if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); - else indentation = 0; - } else if (how == "add") { - indentation = curSpace + cm.options.indentUnit; - } else if (how == "subtract") { - indentation = curSpace - cm.options.indentUnit; - } else if (typeof how == "number") { - indentation = curSpace + how; - } - indentation = Math.max(0, indentation); - - var indentString = "", pos = 0; - if (cm.options.indentWithTabs) - for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} - if (pos < indentation) indentString += spaceStr(indentation - pos); - - if (indentString != curSpaceString) { - replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); - line.stateAfter = null; - return true; - } else { - // Ensure that, if the cursor was in the whitespace at the start - // of the line, it is moved to the end of that space. - for (var i = 0; i < doc.sel.ranges.length; i++) { - var range = doc.sel.ranges[i]; - if (range.head.line == n && range.head.ch < curSpaceString.length) { - var pos = Pos(n, curSpaceString.length); - replaceOneSelection(doc, i, new Range(pos, pos)); - break; - } - } - } - } - - // Utility for applying a change to a line by handle or number, - // returning the number and optionally registering the line as - // changed. - function changeLine(doc, handle, changeType, op) { - var no = handle, line = handle; - if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); - else no = lineNo(handle); - if (no == null) return null; - if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType); - return line; - } - - // Helper for deleting text near the selection(s), used to implement - // backspace, delete, and similar functionality. - function deleteNearSelection(cm, compute) { - var ranges = cm.doc.sel.ranges, kill = []; - // Build up a set of ranges to kill first, merging overlapping - // ranges. - for (var i = 0; i < ranges.length; i++) { - var toKill = compute(ranges[i]); - while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { - var replaced = kill.pop(); - if (cmp(replaced.from, toKill.from) < 0) { - toKill.from = replaced.from; - break; - } - } - kill.push(toKill); - } - // Next, remove those actual ranges. - runInOp(cm, function() { - for (var i = kill.length - 1; i >= 0; i--) - replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); - ensureCursorVisible(cm); - }); - } - - // Used for horizontal relative motion. Dir is -1 or 1 (left or - // right), unit can be "char", "column" (like char, but doesn't - // cross line boundaries), "word" (across next word), or "group" (to - // the start of next group of word or non-word-non-whitespace - // chars). The visually param controls whether, in right-to-left - // text, direction 1 means to move towards the next index in the - // string, or towards the character to the right of the current - // position. The resulting position will have a hitSide=true - // property if it reached the end of the document. - function findPosH(doc, pos, dir, unit, visually) { - var line = pos.line, ch = pos.ch, origDir = dir; - var lineObj = getLine(doc, line); - function findNextLine() { - var l = line + dir; - if (l < doc.first || l >= doc.first + doc.size) return false - line = l; - return lineObj = getLine(doc, l); - } - function moveOnce(boundToLine) { - var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); - if (next == null) { - if (!boundToLine && findNextLine()) { - if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); - else ch = dir < 0 ? lineObj.text.length : 0; - } else return false - } else ch = next; - return true; - } - - if (unit == "char") { - moveOnce() - } else if (unit == "column") { - moveOnce(true) - } else if (unit == "word" || unit == "group") { - var sawType = null, group = unit == "group"; - var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); - for (var first = true;; first = false) { - if (dir < 0 && !moveOnce(!first)) break; - var cur = lineObj.text.charAt(ch) || "\n"; - var type = isWordChar(cur, helper) ? "w" - : group && cur == "\n" ? "n" - : !group || /\s/.test(cur) ? null - : "p"; - if (group && !first && !type) type = "s"; - if (sawType && sawType != type) { - if (dir < 0) {dir = 1; moveOnce();} - break; - } - - if (type) sawType = type; - if (dir > 0 && !moveOnce(!first)) break; - } - } - var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true); - if (!cmp(pos, result)) result.hitSide = true; - return result; - } - - // For relative vertical movement. Dir may be -1 or 1. Unit can be - // "page" or "line". The resulting position will have a hitSide=true - // property if it reached the end of the document. - function findPosV(cm, pos, dir, unit) { - var doc = cm.doc, x = pos.left, y; - if (unit == "page") { - var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); - y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); - } else if (unit == "line") { - y = dir > 0 ? pos.bottom + 3 : pos.top - 3; - } - for (;;) { - var target = coordsChar(cm, x, y); - if (!target.outside) break; - if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } - y += dir * 5; - } - return target; - } - - // EDITOR METHODS - - // The publicly visible API. Note that methodOp(f) means - // 'wrap f in an operation, performed on its `this` parameter'. - - // This is not the complete set of editor methods. Most of the - // methods defined on the Doc type are also injected into - // CodeMirror.prototype, for backwards compatibility and - // convenience. - - CodeMirror.prototype = { - constructor: CodeMirror, - focus: function(){window.focus(); this.display.input.focus();}, - - setOption: function(option, value) { - var options = this.options, old = options[option]; - if (options[option] == value && option != "mode") return; - options[option] = value; - if (optionHandlers.hasOwnProperty(option)) - operation(this, optionHandlers[option])(this, value, old); - }, - - getOption: function(option) {return this.options[option];}, - getDoc: function() {return this.doc;}, - - addKeyMap: function(map, bottom) { - this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); - }, - removeKeyMap: function(map) { - var maps = this.state.keyMaps; - for (var i = 0; i < maps.length; ++i) - if (maps[i] == map || maps[i].name == map) { - maps.splice(i, 1); - return true; - } - }, - - addOverlay: methodOp(function(spec, options) { - var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); - if (mode.startState) throw new Error("Overlays may not be stateful."); - this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); - this.state.modeGen++; - regChange(this); - }), - removeOverlay: methodOp(function(spec) { - var overlays = this.state.overlays; - for (var i = 0; i < overlays.length; ++i) { - var cur = overlays[i].modeSpec; - if (cur == spec || typeof spec == "string" && cur.name == spec) { - overlays.splice(i, 1); - this.state.modeGen++; - regChange(this); - return; - } - } - }), - - indentLine: methodOp(function(n, dir, aggressive) { - if (typeof dir != "string" && typeof dir != "number") { - if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; - else dir = dir ? "add" : "subtract"; - } - if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); - }), - indentSelection: methodOp(function(how) { - var ranges = this.doc.sel.ranges, end = -1; - for (var i = 0; i < ranges.length; i++) { - var range = ranges[i]; - if (!range.empty()) { - var from = range.from(), to = range.to(); - var start = Math.max(end, from.line); - end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; - for (var j = start; j < end; ++j) - indentLine(this, j, how); - var newRanges = this.doc.sel.ranges; - if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) - replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); - } else if (range.head.line > end) { - indentLine(this, range.head.line, how, true); - end = range.head.line; - if (i == this.doc.sel.primIndex) ensureCursorVisible(this); - } - } - }), - - // Fetch the parser token for a given character. Useful for hacks - // that want to inspect the mode state (say, for completion). - getTokenAt: function(pos, precise) { - return takeToken(this, pos, precise); - }, - - getLineTokens: function(line, precise) { - return takeToken(this, Pos(line), precise, true); - }, - - getTokenTypeAt: function(pos) { - pos = clipPos(this.doc, pos); - var styles = getLineStyles(this, getLine(this.doc, pos.line)); - var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; - var type; - if (ch == 0) type = styles[2]; - else for (;;) { - var mid = (before + after) >> 1; - if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; - else if (styles[mid * 2 + 1] < ch) before = mid + 1; - else { type = styles[mid * 2 + 2]; break; } - } - var cut = type ? type.indexOf("cm-overlay ") : -1; - return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); - }, - - getModeAt: function(pos) { - var mode = this.doc.mode; - if (!mode.innerMode) return mode; - return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; - }, - - getHelper: function(pos, type) { - return this.getHelpers(pos, type)[0]; - }, - - getHelpers: function(pos, type) { - var found = []; - if (!helpers.hasOwnProperty(type)) return found; - var help = helpers[type], mode = this.getModeAt(pos); - if (typeof mode[type] == "string") { - if (help[mode[type]]) found.push(help[mode[type]]); - } else if (mode[type]) { - for (var i = 0; i < mode[type].length; i++) { - var val = help[mode[type][i]]; - if (val) found.push(val); - } - } else if (mode.helperType && help[mode.helperType]) { - found.push(help[mode.helperType]); - } else if (help[mode.name]) { - found.push(help[mode.name]); - } - for (var i = 0; i < help._global.length; i++) { - var cur = help._global[i]; - if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) - found.push(cur.val); - } - return found; - }, - - getStateAfter: function(line, precise) { - var doc = this.doc; - line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); - return getStateBefore(this, line + 1, precise); - }, - - cursorCoords: function(start, mode) { - var pos, range = this.doc.sel.primary(); - if (start == null) pos = range.head; - else if (typeof start == "object") pos = clipPos(this.doc, start); - else pos = start ? range.from() : range.to(); - return cursorCoords(this, pos, mode || "page"); - }, - - charCoords: function(pos, mode) { - return charCoords(this, clipPos(this.doc, pos), mode || "page"); - }, - - coordsChar: function(coords, mode) { - coords = fromCoordSystem(this, coords, mode || "page"); - return coordsChar(this, coords.left, coords.top); - }, - - lineAtHeight: function(height, mode) { - height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; - return lineAtHeight(this.doc, height + this.display.viewOffset); - }, - heightAtLine: function(line, mode) { - var end = false, lineObj; - if (typeof line == "number") { - var last = this.doc.first + this.doc.size - 1; - if (line < this.doc.first) line = this.doc.first; - else if (line > last) { line = last; end = true; } - lineObj = getLine(this.doc, line); - } else { - lineObj = line; - } - return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top + - (end ? this.doc.height - heightAtLine(lineObj) : 0); - }, - - defaultTextHeight: function() { return textHeight(this.display); }, - defaultCharWidth: function() { return charWidth(this.display); }, - - setGutterMarker: methodOp(function(line, gutterID, value) { - return changeLine(this.doc, line, "gutter", function(line) { - var markers = line.gutterMarkers || (line.gutterMarkers = {}); - markers[gutterID] = value; - if (!value && isEmpty(markers)) line.gutterMarkers = null; - return true; - }); - }), - - clearGutter: methodOp(function(gutterID) { - var cm = this, doc = cm.doc, i = doc.first; - doc.iter(function(line) { - if (line.gutterMarkers && line.gutterMarkers[gutterID]) { - line.gutterMarkers[gutterID] = null; - regLineChange(cm, i, "gutter"); - if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; - } - ++i; - }); - }), - - lineInfo: function(line) { - if (typeof line == "number") { - if (!isLine(this.doc, line)) return null; - var n = line; - line = getLine(this.doc, line); - if (!line) return null; - } else { - var n = lineNo(line); - if (n == null) return null; - } - return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, - textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, - widgets: line.widgets}; - }, - - getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};}, - - addWidget: function(pos, node, scroll, vert, horiz) { - var display = this.display; - pos = cursorCoords(this, clipPos(this.doc, pos)); - var top = pos.bottom, left = pos.left; - node.style.position = "absolute"; - node.setAttribute("cm-ignore-events", "true"); - this.display.input.setUneditable(node); - display.sizer.appendChild(node); - if (vert == "over") { - top = pos.top; - } else if (vert == "above" || vert == "near") { - var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), - hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); - // Default to positioning above (if specified and possible); otherwise default to positioning below - if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) - top = pos.top - node.offsetHeight; - else if (pos.bottom + node.offsetHeight <= vspace) - top = pos.bottom; - if (left + node.offsetWidth > hspace) - left = hspace - node.offsetWidth; - } - node.style.top = top + "px"; - node.style.left = node.style.right = ""; - if (horiz == "right") { - left = display.sizer.clientWidth - node.offsetWidth; - node.style.right = "0px"; - } else { - if (horiz == "left") left = 0; - else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; - node.style.left = left + "px"; - } - if (scroll) - scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); - }, - - triggerOnKeyDown: methodOp(onKeyDown), - triggerOnKeyPress: methodOp(onKeyPress), - triggerOnKeyUp: onKeyUp, - - execCommand: function(cmd) { - if (commands.hasOwnProperty(cmd)) - return commands[cmd].call(null, this); - }, - - triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), - - findPosH: function(from, amount, unit, visually) { - var dir = 1; - if (amount < 0) { dir = -1; amount = -amount; } - for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { - cur = findPosH(this.doc, cur, dir, unit, visually); - if (cur.hitSide) break; - } - return cur; - }, - - moveH: methodOp(function(dir, unit) { - var cm = this; - cm.extendSelectionsBy(function(range) { - if (cm.display.shift || cm.doc.extend || range.empty()) - return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually); - else - return dir < 0 ? range.from() : range.to(); - }, sel_move); - }), - - deleteH: methodOp(function(dir, unit) { - var sel = this.doc.sel, doc = this.doc; - if (sel.somethingSelected()) - doc.replaceSelection("", null, "+delete"); - else - deleteNearSelection(this, function(range) { - var other = findPosH(doc, range.head, dir, unit, false); - return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}; - }); - }), - - findPosV: function(from, amount, unit, goalColumn) { - var dir = 1, x = goalColumn; - if (amount < 0) { dir = -1; amount = -amount; } - for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { - var coords = cursorCoords(this, cur, "div"); - if (x == null) x = coords.left; - else coords.left = x; - cur = findPosV(this, coords, dir, unit); - if (cur.hitSide) break; - } - return cur; - }, - - moveV: methodOp(function(dir, unit) { - var cm = this, doc = this.doc, goals = []; - var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected(); - doc.extendSelectionsBy(function(range) { - if (collapse) - return dir < 0 ? range.from() : range.to(); - var headPos = cursorCoords(cm, range.head, "div"); - if (range.goalColumn != null) headPos.left = range.goalColumn; - goals.push(headPos.left); - var pos = findPosV(cm, headPos, dir, unit); - if (unit == "page" && range == doc.sel.primary()) - addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top); - return pos; - }, sel_move); - if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++) - doc.sel.ranges[i].goalColumn = goals[i]; - }), - - // Find the word at the given position (as returned by coordsChar). - findWordAt: function(pos) { - var doc = this.doc, line = getLine(doc, pos.line).text; - var start = pos.ch, end = pos.ch; - if (line) { - var helper = this.getHelper(pos, "wordChars"); - if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; - var startChar = line.charAt(start); - var check = isWordChar(startChar, helper) - ? function(ch) { return isWordChar(ch, helper); } - : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} - : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; - while (start > 0 && check(line.charAt(start - 1))) --start; - while (end < line.length && check(line.charAt(end))) ++end; - } - return new Range(Pos(pos.line, start), Pos(pos.line, end)); - }, - - toggleOverwrite: function(value) { - if (value != null && value == this.state.overwrite) return; - if (this.state.overwrite = !this.state.overwrite) - addClass(this.display.cursorDiv, "CodeMirror-overwrite"); - else - rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); - - signal(this, "overwriteToggle", this, this.state.overwrite); - }, - hasFocus: function() { return this.display.input.getField() == activeElt(); }, - isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit); }, - - scrollTo: methodOp(function(x, y) { - if (x != null || y != null) resolveScrollToPos(this); - if (x != null) this.curOp.scrollLeft = x; - if (y != null) this.curOp.scrollTop = y; - }), - getScrollInfo: function() { - var scroller = this.display.scroller; - return {left: scroller.scrollLeft, top: scroller.scrollTop, - height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, - width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, - clientHeight: displayHeight(this), clientWidth: displayWidth(this)}; - }, - - scrollIntoView: methodOp(function(range, margin) { - if (range == null) { - range = {from: this.doc.sel.primary().head, to: null}; - if (margin == null) margin = this.options.cursorScrollMargin; - } else if (typeof range == "number") { - range = {from: Pos(range, 0), to: null}; - } else if (range.from == null) { - range = {from: range, to: null}; - } - if (!range.to) range.to = range.from; - range.margin = margin || 0; - - if (range.from.line != null) { - resolveScrollToPos(this); - this.curOp.scrollToPos = range; - } else { - var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left), - Math.min(range.from.top, range.to.top) - range.margin, - Math.max(range.from.right, range.to.right), - Math.max(range.from.bottom, range.to.bottom) + range.margin); - this.scrollTo(sPos.scrollLeft, sPos.scrollTop); - } - }), - - setSize: methodOp(function(width, height) { - var cm = this; - function interpret(val) { - return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; - } - if (width != null) cm.display.wrapper.style.width = interpret(width); - if (height != null) cm.display.wrapper.style.height = interpret(height); - if (cm.options.lineWrapping) clearLineMeasurementCache(this); - var lineNo = cm.display.viewFrom; - cm.doc.iter(lineNo, cm.display.viewTo, function(line) { - if (line.widgets) for (var i = 0; i < line.widgets.length; i++) - if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; } - ++lineNo; - }); - cm.curOp.forceUpdate = true; - signal(cm, "refresh", this); - }), - - operation: function(f){return runInOp(this, f);}, - - refresh: methodOp(function() { - var oldHeight = this.display.cachedTextHeight; - regChange(this); - this.curOp.forceUpdate = true; - clearCaches(this); - this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); - updateGutterSpace(this); - if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) - estimateLineHeights(this); - signal(this, "refresh", this); - }), - - swapDoc: methodOp(function(doc) { - var old = this.doc; - old.cm = null; - attachDoc(this, doc); - clearCaches(this); - this.display.input.reset(); - this.scrollTo(doc.scrollLeft, doc.scrollTop); - this.curOp.forceScroll = true; - signalLater(this, "swapDoc", this, old); - return old; - }), - - getInputField: function(){return this.display.input.getField();}, - getWrapperElement: function(){return this.display.wrapper;}, - getScrollerElement: function(){return this.display.scroller;}, - getGutterElement: function(){return this.display.gutters;} - }; - eventMixin(CodeMirror); - - // OPTION DEFAULTS - - // The default configuration options. - var defaults = CodeMirror.defaults = {}; - // Functions to run when options are changed. - var optionHandlers = CodeMirror.optionHandlers = {}; - - function option(name, deflt, handle, notOnInit) { - CodeMirror.defaults[name] = deflt; - if (handle) optionHandlers[name] = - notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; - } - - // Passed to option handlers when there is no old value. - var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; - - // These two are, on init, called from the constructor because they - // have to be initialized before the editor can start at all. - option("value", "", function(cm, val) { - cm.setValue(val); - }, true); - option("mode", null, function(cm, val) { - cm.doc.modeOption = val; - loadMode(cm); - }, true); - - option("indentUnit", 2, loadMode, true); - option("indentWithTabs", false); - option("smartIndent", true); - option("tabSize", 4, function(cm) { - resetModeState(cm); - clearCaches(cm); - regChange(cm); - }, true); - option("lineSeparator", null, function(cm, val) { - cm.doc.lineSep = val; - if (!val) return; - var newBreaks = [], lineNo = cm.doc.first; - cm.doc.iter(function(line) { - for (var pos = 0;;) { - var found = line.text.indexOf(val, pos); - if (found == -1) break; - pos = found + val.length; - newBreaks.push(Pos(lineNo, found)); - } - lineNo++; - }); - for (var i = newBreaks.length - 1; i >= 0; i--) - replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) - }); - option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) { - cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); - if (old != CodeMirror.Init) cm.refresh(); - }); - option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); - option("electricChars", true); - option("inputStyle", mobile ? "contenteditable" : "textarea", function() { - throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME - }, true); - option("rtlMoveVisually", !windows); - option("wholeLineUpdateBefore", true); - - option("theme", "default", function(cm) { - themeChanged(cm); - guttersChanged(cm); - }, true); - option("keyMap", "default", function(cm, val, old) { - var next = getKeyMap(val); - var prev = old != CodeMirror.Init && getKeyMap(old); - if (prev && prev.detach) prev.detach(cm, next); - if (next.attach) next.attach(cm, prev || null); - }); - option("extraKeys", null); - - option("lineWrapping", false, wrappingChanged, true); - option("gutters", [], function(cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("fixedGutter", true, function(cm, val) { - cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; - cm.refresh(); - }, true); - option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm);}, true); - option("scrollbarStyle", "native", function(cm) { - initScrollbars(cm); - updateScrollbars(cm); - cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); - cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); - }, true); - option("lineNumbers", false, function(cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("firstLineNumber", 1, guttersChanged, true); - option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); - option("showCursorWhenSelecting", false, updateSelection, true); - - option("resetSelectionOnContextMenu", true); - option("lineWiseCopyCut", true); - - option("readOnly", false, function(cm, val) { - if (val == "nocursor") { - onBlur(cm); - cm.display.input.blur(); - cm.display.disabled = true; - } else { - cm.display.disabled = false; - } - cm.display.input.readOnlyChanged(val) - }); - option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true); - option("dragDrop", true, dragDropChanged); - option("allowDropFileTypes", null); - - option("cursorBlinkRate", 530); - option("cursorScrollMargin", 0); - option("cursorHeight", 1, updateSelection, true); - option("singleCursorHeightPerLine", true, updateSelection, true); - option("workTime", 100); - option("workDelay", 100); - option("flattenSpans", true, resetModeState, true); - option("addModeClass", false, resetModeState, true); - option("pollInterval", 100); - option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); - option("historyEventDelay", 1250); - option("viewportMargin", 10, function(cm){cm.refresh();}, true); - option("maxHighlightLength", 10000, resetModeState, true); - option("moveInputWithCursor", true, function(cm, val) { - if (!val) cm.display.input.resetPosition(); - }); - - option("tabindex", null, function(cm, val) { - cm.display.input.getField().tabIndex = val || ""; - }); - option("autofocus", null); - - // MODE DEFINITION AND QUERYING - - // Known modes, by name and by MIME - var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; - - // Extra arguments are stored as the mode's dependencies, which is - // used by (legacy) mechanisms like loadmode.js to automatically - // load a mode. (Preferred mechanism is the require/define calls.) - CodeMirror.defineMode = function(name, mode) { - if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; - if (arguments.length > 2) - mode.dependencies = Array.prototype.slice.call(arguments, 2); - modes[name] = mode; - }; - - CodeMirror.defineMIME = function(mime, spec) { - mimeModes[mime] = spec; - }; - - // Given a MIME type, a {name, ...options} config object, or a name - // string, return a mode config object. - CodeMirror.resolveMode = function(spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { - spec = mimeModes[spec]; - } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { - var found = mimeModes[spec.name]; - if (typeof found == "string") found = {name: found}; - spec = createObj(found, spec); - spec.name = found.name; - } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { - return CodeMirror.resolveMode("application/xml"); - } - if (typeof spec == "string") return {name: spec}; - else return spec || {name: "null"}; - }; - - // Given a mode spec (anything that resolveMode accepts), find and - // initialize an actual mode object. - CodeMirror.getMode = function(options, spec) { - var spec = CodeMirror.resolveMode(spec); - var mfactory = modes[spec.name]; - if (!mfactory) return CodeMirror.getMode(options, "text/plain"); - var modeObj = mfactory(options, spec); - if (modeExtensions.hasOwnProperty(spec.name)) { - var exts = modeExtensions[spec.name]; - for (var prop in exts) { - if (!exts.hasOwnProperty(prop)) continue; - if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; - modeObj[prop] = exts[prop]; - } - } - modeObj.name = spec.name; - if (spec.helperType) modeObj.helperType = spec.helperType; - if (spec.modeProps) for (var prop in spec.modeProps) - modeObj[prop] = spec.modeProps[prop]; - - return modeObj; - }; - - // Minimal default mode. - CodeMirror.defineMode("null", function() { - return {token: function(stream) {stream.skipToEnd();}}; - }); - CodeMirror.defineMIME("text/plain", "null"); - - // This can be used to attach properties to mode objects from - // outside the actual mode definition. - var modeExtensions = CodeMirror.modeExtensions = {}; - CodeMirror.extendMode = function(mode, properties) { - var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); - copyObj(properties, exts); - }; - - // EXTENSIONS - - CodeMirror.defineExtension = function(name, func) { - CodeMirror.prototype[name] = func; - }; - CodeMirror.defineDocExtension = function(name, func) { - Doc.prototype[name] = func; - }; - CodeMirror.defineOption = option; - - var initHooks = []; - CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; - - var helpers = CodeMirror.helpers = {}; - CodeMirror.registerHelper = function(type, name, value) { - if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; - helpers[type][name] = value; - }; - CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { - CodeMirror.registerHelper(type, name, value); - helpers[type]._global.push({pred: predicate, val: value}); - }; - - // MODE STATE HANDLING - - // Utility functions for working with state. Exported because nested - // modes need to do this for their inner modes. - - var copyState = CodeMirror.copyState = function(mode, state) { - if (state === true) return state; - if (mode.copyState) return mode.copyState(state); - var nstate = {}; - for (var n in state) { - var val = state[n]; - if (val instanceof Array) val = val.concat([]); - nstate[n] = val; - } - return nstate; - }; - - var startState = CodeMirror.startState = function(mode, a1, a2) { - return mode.startState ? mode.startState(a1, a2) : true; - }; - - // Given a mode and a state (for that mode), find the inner mode and - // state at the position that the state refers to. - CodeMirror.innerMode = function(mode, state) { - while (mode.innerMode) { - var info = mode.innerMode(state); - if (!info || info.mode == mode) break; - state = info.state; - mode = info.mode; - } - return info || {mode: mode, state: state}; - }; - - // STANDARD COMMANDS - - // Commands are parameter-less actions that can be performed on an - // editor, mostly used for keybindings. - var commands = CodeMirror.commands = { - selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);}, - singleSelection: function(cm) { - cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); - }, - killLine: function(cm) { - deleteNearSelection(cm, function(range) { - if (range.empty()) { - var len = getLine(cm.doc, range.head.line).text.length; - if (range.head.ch == len && range.head.line < cm.lastLine()) - return {from: range.head, to: Pos(range.head.line + 1, 0)}; - else - return {from: range.head, to: Pos(range.head.line, len)}; - } else { - return {from: range.from(), to: range.to()}; - } - }); - }, - deleteLine: function(cm) { - deleteNearSelection(cm, function(range) { - return {from: Pos(range.from().line, 0), - to: clipPos(cm.doc, Pos(range.to().line + 1, 0))}; - }); - }, - delLineLeft: function(cm) { - deleteNearSelection(cm, function(range) { - return {from: Pos(range.from().line, 0), to: range.from()}; - }); - }, - delWrappedLineLeft: function(cm) { - deleteNearSelection(cm, function(range) { - var top = cm.charCoords(range.head, "div").top + 5; - var leftPos = cm.coordsChar({left: 0, top: top}, "div"); - return {from: leftPos, to: range.from()}; - }); - }, - delWrappedLineRight: function(cm) { - deleteNearSelection(cm, function(range) { - var top = cm.charCoords(range.head, "div").top + 5; - var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); - return {from: range.from(), to: rightPos }; - }); - }, - undo: function(cm) {cm.undo();}, - redo: function(cm) {cm.redo();}, - undoSelection: function(cm) {cm.undoSelection();}, - redoSelection: function(cm) {cm.redoSelection();}, - goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, - goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, - goLineStart: function(cm) { - cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, - {origin: "+move", bias: 1}); - }, - goLineStartSmart: function(cm) { - cm.extendSelectionsBy(function(range) { - return lineStartSmart(cm, range.head); - }, {origin: "+move", bias: 1}); - }, - goLineEnd: function(cm) { - cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, - {origin: "+move", bias: -1}); - }, - goLineRight: function(cm) { - cm.extendSelectionsBy(function(range) { - var top = cm.charCoords(range.head, "div").top + 5; - return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); - }, sel_move); - }, - goLineLeft: function(cm) { - cm.extendSelectionsBy(function(range) { - var top = cm.charCoords(range.head, "div").top + 5; - return cm.coordsChar({left: 0, top: top}, "div"); - }, sel_move); - }, - goLineLeftSmart: function(cm) { - cm.extendSelectionsBy(function(range) { - var top = cm.charCoords(range.head, "div").top + 5; - var pos = cm.coordsChar({left: 0, top: top}, "div"); - if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); - return pos; - }, sel_move); - }, - goLineUp: function(cm) {cm.moveV(-1, "line");}, - goLineDown: function(cm) {cm.moveV(1, "line");}, - goPageUp: function(cm) {cm.moveV(-1, "page");}, - goPageDown: function(cm) {cm.moveV(1, "page");}, - goCharLeft: function(cm) {cm.moveH(-1, "char");}, - goCharRight: function(cm) {cm.moveH(1, "char");}, - goColumnLeft: function(cm) {cm.moveH(-1, "column");}, - goColumnRight: function(cm) {cm.moveH(1, "column");}, - goWordLeft: function(cm) {cm.moveH(-1, "word");}, - goGroupRight: function(cm) {cm.moveH(1, "group");}, - goGroupLeft: function(cm) {cm.moveH(-1, "group");}, - goWordRight: function(cm) {cm.moveH(1, "word");}, - delCharBefore: function(cm) {cm.deleteH(-1, "char");}, - delCharAfter: function(cm) {cm.deleteH(1, "char");}, - delWordBefore: function(cm) {cm.deleteH(-1, "word");}, - delWordAfter: function(cm) {cm.deleteH(1, "word");}, - delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, - delGroupAfter: function(cm) {cm.deleteH(1, "group");}, - indentAuto: function(cm) {cm.indentSelection("smart");}, - indentMore: function(cm) {cm.indentSelection("add");}, - indentLess: function(cm) {cm.indentSelection("subtract");}, - insertTab: function(cm) {cm.replaceSelection("\t");}, - insertSoftTab: function(cm) { - var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; - for (var i = 0; i < ranges.length; i++) { - var pos = ranges[i].from(); - var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); - spaces.push(new Array(tabSize - col % tabSize + 1).join(" ")); - } - cm.replaceSelections(spaces); - }, - defaultTab: function(cm) { - if (cm.somethingSelected()) cm.indentSelection("add"); - else cm.execCommand("insertTab"); - }, - transposeChars: function(cm) { - runInOp(cm, function() { - var ranges = cm.listSelections(), newSel = []; - for (var i = 0; i < ranges.length; i++) { - var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; - if (line) { - if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); - if (cur.ch > 0) { - cur = new Pos(cur.line, cur.ch + 1); - cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), - Pos(cur.line, cur.ch - 2), cur, "+transpose"); - } else if (cur.line > cm.doc.first) { - var prev = getLine(cm.doc, cur.line - 1).text; - if (prev) - cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() + - prev.charAt(prev.length - 1), - Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose"); - } - } - newSel.push(new Range(cur, cur)); - } - cm.setSelections(newSel); - }); - }, - newlineAndIndent: function(cm) { - runInOp(cm, function() { - var len = cm.listSelections().length; - for (var i = 0; i < len; i++) { - var range = cm.listSelections()[i]; - cm.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+input"); - cm.indentLine(range.from().line + 1, null, true); - } - ensureCursorVisible(cm); - }); - }, - toggleOverwrite: function(cm) {cm.toggleOverwrite();} - }; - - - // STANDARD KEYMAPS - - var keyMap = CodeMirror.keyMap = {}; - - keyMap.basic = { - "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", - "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", - "Tab": "defaultTab", "Shift-Tab": "indentAuto", - "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", - "Esc": "singleSelection" - }; - // Note that the save and find-related commands aren't defined by - // default. User code or addons can define them. Unknown commands - // are simply ignored. - keyMap.pcDefault = { - "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", - "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", - "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", - "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", - "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", - "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", - "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", - fallthrough: "basic" - }; - // Very basic readline/emacs-style bindings, which are standard on Mac. - keyMap.emacsy = { - "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", - "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", - "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", - "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" - }; - keyMap.macDefault = { - "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", - "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", - "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", - "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", - "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", - "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", - "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", - fallthrough: ["basic", "emacsy"] - }; - keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; - - // KEYMAP DISPATCH - - function normalizeKeyName(name) { - var parts = name.split(/-(?!$)/), name = parts[parts.length - 1]; - var alt, ctrl, shift, cmd; - for (var i = 0; i < parts.length - 1; i++) { - var mod = parts[i]; - if (/^(cmd|meta|m)$/i.test(mod)) cmd = true; - else if (/^a(lt)?$/i.test(mod)) alt = true; - else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true; - else if (/^s(hift)$/i.test(mod)) shift = true; - else throw new Error("Unrecognized modifier name: " + mod); - } - if (alt) name = "Alt-" + name; - if (ctrl) name = "Ctrl-" + name; - if (cmd) name = "Cmd-" + name; - if (shift) name = "Shift-" + name; - return name; - } - - // This is a kludge to keep keymaps mostly working as raw objects - // (backwards compatibility) while at the same time support features - // like normalization and multi-stroke key bindings. It compiles a - // new normalized keymap, and then updates the old object to reflect - // this. - CodeMirror.normalizeKeyMap = function(keymap) { - var copy = {}; - for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { - var value = keymap[keyname]; - if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; - if (value == "...") { delete keymap[keyname]; continue; } - - var keys = map(keyname.split(" "), normalizeKeyName); - for (var i = 0; i < keys.length; i++) { - var val, name; - if (i == keys.length - 1) { - name = keys.join(" "); - val = value; - } else { - name = keys.slice(0, i + 1).join(" "); - val = "..."; - } - var prev = copy[name]; - if (!prev) copy[name] = val; - else if (prev != val) throw new Error("Inconsistent bindings for " + name); - } - delete keymap[keyname]; - } - for (var prop in copy) keymap[prop] = copy[prop]; - return keymap; - }; - - var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) { - map = getKeyMap(map); - var found = map.call ? map.call(key, context) : map[key]; - if (found === false) return "nothing"; - if (found === "...") return "multi"; - if (found != null && handle(found)) return "handled"; - - if (map.fallthrough) { - if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") - return lookupKey(key, map.fallthrough, handle, context); - for (var i = 0; i < map.fallthrough.length; i++) { - var result = lookupKey(key, map.fallthrough[i], handle, context); - if (result) return result; - } - } - }; - - // Modifier key presses don't count as 'real' key presses for the - // purpose of keymap fallthrough. - var isModifierKey = CodeMirror.isModifierKey = function(value) { - var name = typeof value == "string" ? value : keyNames[value.keyCode]; - return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; - }; - - // Look up the name of a key as indicated by an event object. - var keyName = CodeMirror.keyName = function(event, noShift) { - if (presto && event.keyCode == 34 && event["char"]) return false; - var base = keyNames[event.keyCode], name = base; - if (name == null || event.altGraphKey) return false; - if (event.altKey && base != "Alt") name = "Alt-" + name; - if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name; - if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name; - if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name; - return name; - }; - - function getKeyMap(val) { - return typeof val == "string" ? keyMap[val] : val; - } - - // FROMTEXTAREA - - CodeMirror.fromTextArea = function(textarea, options) { - options = options ? copyObj(options) : {}; - options.value = textarea.value; - if (!options.tabindex && textarea.tabIndex) - options.tabindex = textarea.tabIndex; - if (!options.placeholder && textarea.placeholder) - options.placeholder = textarea.placeholder; - // Set autofocus to true if this textarea is focused, or if it has - // autofocus and no other element is focused. - if (options.autofocus == null) { - var hasFocus = activeElt(); - options.autofocus = hasFocus == textarea || - textarea.getAttribute("autofocus") != null && hasFocus == document.body; - } - - function save() {textarea.value = cm.getValue();} - if (textarea.form) { - on(textarea.form, "submit", save); - // Deplorable hack to make the submit method do the right thing. - if (!options.leaveSubmitMethodAlone) { - var form = textarea.form, realSubmit = form.submit; - try { - var wrappedSubmit = form.submit = function() { - save(); - form.submit = realSubmit; - form.submit(); - form.submit = wrappedSubmit; - }; - } catch(e) {} - } - } - - options.finishInit = function(cm) { - cm.save = save; - cm.getTextArea = function() { return textarea; }; - cm.toTextArea = function() { - cm.toTextArea = isNaN; // Prevent this from being ran twice - save(); - textarea.parentNode.removeChild(cm.getWrapperElement()); - textarea.style.display = ""; - if (textarea.form) { - off(textarea.form, "submit", save); - if (typeof textarea.form.submit == "function") - textarea.form.submit = realSubmit; - } - }; - }; - - textarea.style.display = "none"; - var cm = CodeMirror(function(node) { - textarea.parentNode.insertBefore(node, textarea.nextSibling); - }, options); - return cm; - }; - - // STRING STREAM - - // Fed to the mode parsers, provides helper functions to make - // parsers more succinct. - - var StringStream = CodeMirror.StringStream = function(string, tabSize) { - this.pos = this.start = 0; - this.string = string; - this.tabSize = tabSize || 8; - this.lastColumnPos = this.lastColumnValue = 0; - this.lineStart = 0; - }; - - StringStream.prototype = { - eol: function() {return this.pos >= this.string.length;}, - sol: function() {return this.pos == this.lineStart;}, - peek: function() {return this.string.charAt(this.pos) || undefined;}, - next: function() { - if (this.pos < this.string.length) - return this.string.charAt(this.pos++); - }, - eat: function(match) { - var ch = this.string.charAt(this.pos); - if (typeof match == "string") var ok = ch == match; - else var ok = ch && (match.test ? match.test(ch) : match(ch)); - if (ok) {++this.pos; return ch;} - }, - eatWhile: function(match) { - var start = this.pos; - while (this.eat(match)){} - return this.pos > start; - }, - eatSpace: function() { - var start = this.pos; - while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; - return this.pos > start; - }, - skipToEnd: function() {this.pos = this.string.length;}, - skipTo: function(ch) { - var found = this.string.indexOf(ch, this.pos); - if (found > -1) {this.pos = found; return true;} - }, - backUp: function(n) {this.pos -= n;}, - column: function() { - if (this.lastColumnPos < this.start) { - this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); - this.lastColumnPos = this.start; - } - return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); - }, - indentation: function() { - return countColumn(this.string, null, this.tabSize) - - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); - }, - match: function(pattern, consume, caseInsensitive) { - if (typeof pattern == "string") { - var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - var substr = this.string.substr(this.pos, pattern.length); - if (cased(substr) == cased(pattern)) { - if (consume !== false) this.pos += pattern.length; - return true; - } - } else { - var match = this.string.slice(this.pos).match(pattern); - if (match && match.index > 0) return null; - if (match && consume !== false) this.pos += match[0].length; - return match; - } - }, - current: function(){return this.string.slice(this.start, this.pos);}, - hideFirstChars: function(n, inner) { - this.lineStart += n; - try { return inner(); } - finally { this.lineStart -= n; } - } - }; - - // TEXTMARKERS - - // Created with markText and setBookmark methods. A TextMarker is a - // handle that can be used to clear or find a marked position in the - // document. Line objects hold arrays (markedSpans) containing - // {from, to, marker} object pointing to such marker objects, and - // indicating that such a marker is present on that line. Multiple - // lines may point to the same marker when it spans across lines. - // The spans will have null for their from/to properties when the - // marker continues beyond the start/end of the line. Markers have - // links back to the lines they currently touch. - - var nextMarkerId = 0; - - var TextMarker = CodeMirror.TextMarker = function(doc, type) { - this.lines = []; - this.type = type; - this.doc = doc; - this.id = ++nextMarkerId; - }; - eventMixin(TextMarker); - - // Clear the marker. - TextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return; - var cm = this.doc.cm, withOp = cm && !cm.curOp; - if (withOp) startOperation(cm); - if (hasHandler(this, "clear")) { - var found = this.find(); - if (found) signalLater(this, "clear", found.from, found.to); - } - var min = null, max = null; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text"); - else if (cm) { - if (span.to != null) max = lineNo(line); - if (span.from != null) min = lineNo(line); - } - line.markedSpans = removeMarkedSpan(line.markedSpans, span); - if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) - updateLineHeight(line, textHeight(cm.display)); - } - if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { - var visual = visualLine(this.lines[i]), len = lineLength(visual); - if (len > cm.display.maxLineLength) { - cm.display.maxLine = visual; - cm.display.maxLineLength = len; - cm.display.maxLineChanged = true; - } - } - - if (min != null && cm && this.collapsed) regChange(cm, min, max + 1); - this.lines.length = 0; - this.explicitlyCleared = true; - if (this.atomic && this.doc.cantEdit) { - this.doc.cantEdit = false; - if (cm) reCheckSelection(cm.doc); - } - if (cm) signalLater(cm, "markerCleared", cm, this); - if (withOp) endOperation(cm); - if (this.parent) this.parent.clear(); - }; - - // Find the position of the marker in the document. Returns a {from, - // to} object by default. Side can be passed to get a specific side - // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the - // Pos objects returned contain a line object, rather than a line - // number (used to prevent looking up the same line twice). - TextMarker.prototype.find = function(side, lineObj) { - if (side == null && this.type == "bookmark") side = 1; - var from, to; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (span.from != null) { - from = Pos(lineObj ? line : lineNo(line), span.from); - if (side == -1) return from; - } - if (span.to != null) { - to = Pos(lineObj ? line : lineNo(line), span.to); - if (side == 1) return to; - } - } - return from && {from: from, to: to}; - }; - - // Signals that the marker's widget changed, and surrounding layout - // should be recomputed. - TextMarker.prototype.changed = function() { - var pos = this.find(-1, true), widget = this, cm = this.doc.cm; - if (!pos || !cm) return; - runInOp(cm, function() { - var line = pos.line, lineN = lineNo(pos.line); - var view = findViewForLine(cm, lineN); - if (view) { - clearLineMeasurementCacheFor(view); - cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; - } - cm.curOp.updateMaxLine = true; - if (!lineIsHidden(widget.doc, line) && widget.height != null) { - var oldHeight = widget.height; - widget.height = null; - var dHeight = widgetHeight(widget) - oldHeight; - if (dHeight) - updateLineHeight(line, line.height + dHeight); - } - }); - }; - - TextMarker.prototype.attachLine = function(line) { - if (!this.lines.length && this.doc.cm) { - var op = this.doc.cm.curOp; - if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) - (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); - } - this.lines.push(line); - }; - TextMarker.prototype.detachLine = function(line) { - this.lines.splice(indexOf(this.lines, line), 1); - if (!this.lines.length && this.doc.cm) { - var op = this.doc.cm.curOp; - (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); - } - }; - - // Collapsed markers have unique ids, in order to be able to order - // them, which is needed for uniquely determining an outer marker - // when they overlap (they may nest, but not partially overlap). - var nextMarkerId = 0; - - // Create a marker, wire it up to the right lines, and - function markText(doc, from, to, options, type) { - // Shared markers (across linked documents) are handled separately - // (markTextShared will call out to this again, once per - // document). - if (options && options.shared) return markTextShared(doc, from, to, options, type); - // Ensure we are in an operation. - if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); - - var marker = new TextMarker(doc, type), diff = cmp(from, to); - if (options) copyObj(options, marker, false); - // Don't connect empty markers unless clearWhenEmpty is false - if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) - return marker; - if (marker.replacedWith) { - // Showing up as a widget implies collapsed (widget replaces text) - marker.collapsed = true; - marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"); - if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true"); - if (options.insertLeft) marker.widgetNode.insertLeft = true; - } - if (marker.collapsed) { - if (conflictingCollapsedRange(doc, from.line, from, to, marker) || - from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) - throw new Error("Inserting collapsed marker partially overlapping an existing one"); - sawCollapsedSpans = true; - } - - if (marker.addToHistory) - addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); - - var curLine = from.line, cm = doc.cm, updateMaxLine; - doc.iter(curLine, to.line + 1, function(line) { - if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) - updateMaxLine = true; - if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); - addMarkedSpan(line, new MarkedSpan(marker, - curLine == from.line ? from.ch : null, - curLine == to.line ? to.ch : null)); - ++curLine; - }); - // lineIsHidden depends on the presence of the spans, so needs a second pass - if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { - if (lineIsHidden(doc, line)) updateLineHeight(line, 0); - }); - - if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); - - if (marker.readOnly) { - sawReadOnlySpans = true; - if (doc.history.done.length || doc.history.undone.length) - doc.clearHistory(); - } - if (marker.collapsed) { - marker.id = ++nextMarkerId; - marker.atomic = true; - } - if (cm) { - // Sync editor state - if (updateMaxLine) cm.curOp.updateMaxLine = true; - if (marker.collapsed) - regChange(cm, from.line, to.line + 1); - else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) - for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); - if (marker.atomic) reCheckSelection(cm.doc); - signalLater(cm, "markerAdded", cm, marker); - } - return marker; - } - - // SHARED TEXTMARKERS - - // A shared marker spans multiple linked documents. It is - // implemented as a meta-marker-object controlling multiple normal - // markers. - var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) { - this.markers = markers; - this.primary = primary; - for (var i = 0; i < markers.length; ++i) - markers[i].parent = this; - }; - eventMixin(SharedTextMarker); - - SharedTextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return; - this.explicitlyCleared = true; - for (var i = 0; i < this.markers.length; ++i) - this.markers[i].clear(); - signalLater(this, "clear"); - }; - SharedTextMarker.prototype.find = function(side, lineObj) { - return this.primary.find(side, lineObj); - }; - - function markTextShared(doc, from, to, options, type) { - options = copyObj(options); - options.shared = false; - var markers = [markText(doc, from, to, options, type)], primary = markers[0]; - var widget = options.widgetNode; - linkedDocs(doc, function(doc) { - if (widget) options.widgetNode = widget.cloneNode(true); - markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); - for (var i = 0; i < doc.linked.length; ++i) - if (doc.linked[i].isParent) return; - primary = lst(markers); - }); - return new SharedTextMarker(markers, primary); - } - - function findSharedMarkers(doc) { - return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), - function(m) { return m.parent; }); - } - - function copySharedMarkers(doc, markers) { - for (var i = 0; i < markers.length; i++) { - var marker = markers[i], pos = marker.find(); - var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); - if (cmp(mFrom, mTo)) { - var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); - marker.markers.push(subMark); - subMark.parent = marker; - } - } - } - - function detachSharedMarkers(markers) { - for (var i = 0; i < markers.length; i++) { - var marker = markers[i], linked = [marker.primary.doc];; - linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); - for (var j = 0; j < marker.markers.length; j++) { - var subMarker = marker.markers[j]; - if (indexOf(linked, subMarker.doc) == -1) { - subMarker.parent = null; - marker.markers.splice(j--, 1); - } - } - } - } - - // TEXTMARKER SPANS - - function MarkedSpan(marker, from, to) { - this.marker = marker; - this.from = from; this.to = to; - } - - // Search an array of spans for a span matching the given marker. - function getMarkedSpanFor(spans, marker) { - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if (span.marker == marker) return span; - } - } - // Remove a span from an array, returning undefined if no spans are - // left (we don't store arrays for lines without spans). - function removeMarkedSpan(spans, span) { - for (var r, i = 0; i < spans.length; ++i) - if (spans[i] != span) (r || (r = [])).push(spans[i]); - return r; - } - // Add a span to a line. - function addMarkedSpan(line, span) { - line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; - span.marker.attachLine(line); - } - - // Used for the algorithm that adjusts markers for a change in the - // document. These functions cut an array of spans at a given - // character position, returning an array of remaining chunks (or - // undefined if nothing remains). - function markedSpansBefore(old, startCh, isInsert) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); - if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); - (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); - } - } - return nw; - } - function markedSpansAfter(old, endCh, isInsert) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); - if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); - (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, - span.to == null ? null : span.to - endCh)); - } - } - return nw; - } - - // Given a change object, compute the new set of marker spans that - // cover the line in which the change took place. Removes spans - // entirely within the change, reconnects spans belonging to the - // same marker that appear on both sides of the change, and cuts off - // spans partially within the change. Returns an array of span - // arrays with one element for each line in (after) the change. - function stretchSpansOverChange(doc, change) { - if (change.full) return null; - var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; - var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; - if (!oldFirst && !oldLast) return null; - - var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; - // Get the spans that 'stick out' on both sides - var first = markedSpansBefore(oldFirst, startCh, isInsert); - var last = markedSpansAfter(oldLast, endCh, isInsert); - - // Next, merge those two ends - var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); - if (first) { - // Fix up .to properties of first - for (var i = 0; i < first.length; ++i) { - var span = first[i]; - if (span.to == null) { - var found = getMarkedSpanFor(last, span.marker); - if (!found) span.to = startCh; - else if (sameLine) span.to = found.to == null ? null : found.to + offset; - } - } - } - if (last) { - // Fix up .from in last (or move them into first in case of sameLine) - for (var i = 0; i < last.length; ++i) { - var span = last[i]; - if (span.to != null) span.to += offset; - if (span.from == null) { - var found = getMarkedSpanFor(first, span.marker); - if (!found) { - span.from = offset; - if (sameLine) (first || (first = [])).push(span); - } - } else { - span.from += offset; - if (sameLine) (first || (first = [])).push(span); - } - } - } - // Make sure we didn't create any zero-length spans - if (first) first = clearEmptySpans(first); - if (last && last != first) last = clearEmptySpans(last); - - var newMarkers = [first]; - if (!sameLine) { - // Fill gap with whole-line-spans - var gap = change.text.length - 2, gapMarkers; - if (gap > 0 && first) - for (var i = 0; i < first.length; ++i) - if (first[i].to == null) - (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)); - for (var i = 0; i < gap; ++i) - newMarkers.push(gapMarkers); - newMarkers.push(last); - } - return newMarkers; - } - - // Remove spans that are empty and don't have a clearWhenEmpty - // option of false. - function clearEmptySpans(spans) { - for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) - spans.splice(i--, 1); - } - if (!spans.length) return null; - return spans; - } - - // Used for un/re-doing changes from the history. Combines the - // result of computing the existing spans with the set of spans that - // existed in the history (so that deleting around a span and then - // undoing brings back the span). - function mergeOldSpans(doc, change) { - var old = getOldSpans(doc, change); - var stretched = stretchSpansOverChange(doc, change); - if (!old) return stretched; - if (!stretched) return old; - - for (var i = 0; i < old.length; ++i) { - var oldCur = old[i], stretchCur = stretched[i]; - if (oldCur && stretchCur) { - spans: for (var j = 0; j < stretchCur.length; ++j) { - var span = stretchCur[j]; - for (var k = 0; k < oldCur.length; ++k) - if (oldCur[k].marker == span.marker) continue spans; - oldCur.push(span); - } - } else if (stretchCur) { - old[i] = stretchCur; - } - } - return old; - } - - // Used to 'clip' out readOnly ranges when making a change. - function removeReadOnlyRanges(doc, from, to) { - var markers = null; - doc.iter(from.line, to.line + 1, function(line) { - if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { - var mark = line.markedSpans[i].marker; - if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) - (markers || (markers = [])).push(mark); - } - }); - if (!markers) return null; - var parts = [{from: from, to: to}]; - for (var i = 0; i < markers.length; ++i) { - var mk = markers[i], m = mk.find(0); - for (var j = 0; j < parts.length; ++j) { - var p = parts[j]; - if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue; - var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); - if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) - newParts.push({from: p.from, to: m.from}); - if (dto > 0 || !mk.inclusiveRight && !dto) - newParts.push({from: m.to, to: p.to}); - parts.splice.apply(parts, newParts); - j += newParts.length - 1; - } - } - return parts; - } - - // Connect or disconnect spans from a line. - function detachMarkedSpans(line) { - var spans = line.markedSpans; - if (!spans) return; - for (var i = 0; i < spans.length; ++i) - spans[i].marker.detachLine(line); - line.markedSpans = null; - } - function attachMarkedSpans(line, spans) { - if (!spans) return; - for (var i = 0; i < spans.length; ++i) - spans[i].marker.attachLine(line); - line.markedSpans = spans; - } - - // Helpers used when computing which overlapping collapsed span - // counts as the larger one. - function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } - function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } - - // Returns a number indicating which of two overlapping collapsed - // spans is larger (and thus includes the other). Falls back to - // comparing ids when the spans cover exactly the same range. - function compareCollapsedMarkers(a, b) { - var lenDiff = a.lines.length - b.lines.length; - if (lenDiff != 0) return lenDiff; - var aPos = a.find(), bPos = b.find(); - var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); - if (fromCmp) return -fromCmp; - var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); - if (toCmp) return toCmp; - return b.id - a.id; - } - - // Find out whether a line ends or starts in a collapsed span. If - // so, return the marker for that span. - function collapsedSpanAtSide(line, start) { - var sps = sawCollapsedSpans && line.markedSpans, found; - if (sps) for (var sp, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && - (!found || compareCollapsedMarkers(found, sp.marker) < 0)) - found = sp.marker; - } - return found; - } - function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); } - function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } - - // Test whether there exists a collapsed span that partially - // overlaps (covers the start or end, but not both) of a new span. - // Such overlap is not allowed. - function conflictingCollapsedRange(doc, lineNo, from, to, marker) { - var line = getLine(doc, lineNo); - var sps = sawCollapsedSpans && line.markedSpans; - if (sps) for (var i = 0; i < sps.length; ++i) { - var sp = sps[i]; - if (!sp.marker.collapsed) continue; - var found = sp.marker.find(0); - var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); - var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); - if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; - if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) || - fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight))) - return true; - } - } - - // A visual line is a line as drawn on the screen. Folding, for - // example, can cause multiple logical lines to appear on the same - // visual line. This finds the start of the visual line that the - // given line is part of (usually that is the line itself). - function visualLine(line) { - var merged; - while (merged = collapsedSpanAtStart(line)) - line = merged.find(-1, true).line; - return line; - } - - // Returns an array of logical lines that continue the visual line - // started by the argument, or undefined if there are no such lines. - function visualLineContinued(line) { - var merged, lines; - while (merged = collapsedSpanAtEnd(line)) { - line = merged.find(1, true).line; - (lines || (lines = [])).push(line); - } - return lines; - } - - // Get the line number of the start of the visual line that the - // given line number is part of. - function visualLineNo(doc, lineN) { - var line = getLine(doc, lineN), vis = visualLine(line); - if (line == vis) return lineN; - return lineNo(vis); - } - // Get the line number of the start of the next visual line after - // the given line. - function visualLineEndNo(doc, lineN) { - if (lineN > doc.lastLine()) return lineN; - var line = getLine(doc, lineN), merged; - if (!lineIsHidden(doc, line)) return lineN; - while (merged = collapsedSpanAtEnd(line)) - line = merged.find(1, true).line; - return lineNo(line) + 1; - } - - // Compute whether a line is hidden. Lines count as hidden when they - // are part of a visual line that starts with another line, or when - // they are entirely covered by collapsed, non-widget span. - function lineIsHidden(doc, line) { - var sps = sawCollapsedSpans && line.markedSpans; - if (sps) for (var sp, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (!sp.marker.collapsed) continue; - if (sp.from == null) return true; - if (sp.marker.widgetNode) continue; - if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) - return true; - } - } - function lineIsHiddenInner(doc, line, span) { - if (span.to == null) { - var end = span.marker.find(1, true); - return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)); - } - if (span.marker.inclusiveRight && span.to == line.text.length) - return true; - for (var sp, i = 0; i < line.markedSpans.length; ++i) { - sp = line.markedSpans[i]; - if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && - (sp.to == null || sp.to != span.from) && - (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && - lineIsHiddenInner(doc, line, sp)) return true; - } - } - - // LINE WIDGETS - - // Line widgets are block elements displayed above or below a line. - - var LineWidget = CodeMirror.LineWidget = function(doc, node, options) { - if (options) for (var opt in options) if (options.hasOwnProperty(opt)) - this[opt] = options[opt]; - this.doc = doc; - this.node = node; - }; - eventMixin(LineWidget); - - function adjustScrollWhenAboveVisible(cm, line, diff) { - if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) - addToScrollPos(cm, null, diff); - } - - LineWidget.prototype.clear = function() { - var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); - if (no == null || !ws) return; - for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); - if (!ws.length) line.widgets = null; - var height = widgetHeight(this); - updateLineHeight(line, Math.max(0, line.height - height)); - if (cm) runInOp(cm, function() { - adjustScrollWhenAboveVisible(cm, line, -height); - regLineChange(cm, no, "widget"); - }); - }; - LineWidget.prototype.changed = function() { - var oldH = this.height, cm = this.doc.cm, line = this.line; - this.height = null; - var diff = widgetHeight(this) - oldH; - if (!diff) return; - updateLineHeight(line, line.height + diff); - if (cm) runInOp(cm, function() { - cm.curOp.forceUpdate = true; - adjustScrollWhenAboveVisible(cm, line, diff); - }); - }; - - function widgetHeight(widget) { - if (widget.height != null) return widget.height; - var cm = widget.doc.cm; - if (!cm) return 0; - if (!contains(document.body, widget.node)) { - var parentStyle = "position: relative;"; - if (widget.coverGutter) - parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; - if (widget.noHScroll) - parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; - removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); - } - return widget.height = widget.node.parentNode.offsetHeight; - } - - function addLineWidget(doc, handle, node, options) { - var widget = new LineWidget(doc, node, options); - var cm = doc.cm; - if (cm && widget.noHScroll) cm.display.alignWidgets = true; - changeLine(doc, handle, "widget", function(line) { - var widgets = line.widgets || (line.widgets = []); - if (widget.insertAt == null) widgets.push(widget); - else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); - widget.line = line; - if (cm && !lineIsHidden(doc, line)) { - var aboveVisible = heightAtLine(line) < doc.scrollTop; - updateLineHeight(line, line.height + widgetHeight(widget)); - if (aboveVisible) addToScrollPos(cm, null, widget.height); - cm.curOp.forceUpdate = true; - } - return true; - }); - return widget; - } - - // LINE DATA STRUCTURE - - // Line objects. These hold state related to a line, including - // highlighting info (the styles array). - var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { - this.text = text; - attachMarkedSpans(this, markedSpans); - this.height = estimateHeight ? estimateHeight(this) : 1; - }; - eventMixin(Line); - Line.prototype.lineNo = function() { return lineNo(this); }; - - // Change the content (text, markers) of a line. Automatically - // invalidates cached information and tries to re-estimate the - // line's height. - function updateLine(line, text, markedSpans, estimateHeight) { - line.text = text; - if (line.stateAfter) line.stateAfter = null; - if (line.styles) line.styles = null; - if (line.order != null) line.order = null; - detachMarkedSpans(line); - attachMarkedSpans(line, markedSpans); - var estHeight = estimateHeight ? estimateHeight(line) : 1; - if (estHeight != line.height) updateLineHeight(line, estHeight); - } - - // Detach a line from the document tree and its markers. - function cleanUpLine(line) { - line.parent = null; - detachMarkedSpans(line); - } - - function extractLineClasses(type, output) { - if (type) for (;;) { - var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); - if (!lineClass) break; - type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); - var prop = lineClass[1] ? "bgClass" : "textClass"; - if (output[prop] == null) - output[prop] = lineClass[2]; - else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) - output[prop] += " " + lineClass[2]; - } - return type; - } - - function callBlankLine(mode, state) { - if (mode.blankLine) return mode.blankLine(state); - if (!mode.innerMode) return; - var inner = CodeMirror.innerMode(mode, state); - if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); - } - - function readToken(mode, stream, state, inner) { - for (var i = 0; i < 10; i++) { - if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode; - var style = mode.token(stream, state); - if (stream.pos > stream.start) return style; - } - throw new Error("Mode " + mode.name + " failed to advance stream."); - } - - // Utility for getTokenAt and getLineTokens - function takeToken(cm, pos, precise, asArray) { - function getObj(copy) { - return {start: stream.start, end: stream.pos, - string: stream.current(), - type: style || null, - state: copy ? copyState(doc.mode, state) : state}; - } - - var doc = cm.doc, mode = doc.mode, style; - pos = clipPos(doc, pos); - var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise); - var stream = new StringStream(line.text, cm.options.tabSize), tokens; - if (asArray) tokens = []; - while ((asArray || stream.pos < pos.ch) && !stream.eol()) { - stream.start = stream.pos; - style = readToken(mode, stream, state); - if (asArray) tokens.push(getObj(true)); - } - return asArray ? tokens : getObj(); - } - - // Run the given mode's parser over a line, calling f for each token. - function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { - var flattenSpans = mode.flattenSpans; - if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; - var curStart = 0, curStyle = null; - var stream = new StringStream(text, cm.options.tabSize), style; - var inner = cm.options.addModeClass && [null]; - if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); - while (!stream.eol()) { - if (stream.pos > cm.options.maxHighlightLength) { - flattenSpans = false; - if (forceToEnd) processLine(cm, text, state, stream.pos); - stream.pos = text.length; - style = null; - } else { - style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses); - } - if (inner) { - var mName = inner[0].name; - if (mName) style = "m-" + (style ? mName + " " + style : mName); - } - if (!flattenSpans || curStyle != style) { - while (curStart < stream.start) { - curStart = Math.min(stream.start, curStart + 50000); - f(curStart, curStyle); - } - curStyle = style; - } - stream.start = stream.pos; - } - while (curStart < stream.pos) { - // Webkit seems to refuse to render text nodes longer than 57444 characters - var pos = Math.min(stream.pos, curStart + 50000); - f(pos, curStyle); - curStart = pos; - } - } - - // Compute a style array (an array starting with a mode generation - // -- for invalidation -- followed by pairs of end positions and - // style strings), which is used to highlight the tokens on the - // line. - function highlightLine(cm, line, state, forceToEnd) { - // A styles array always starts with a number identifying the - // mode/overlays that it is based on (for easy invalidation). - var st = [cm.state.modeGen], lineClasses = {}; - // Compute the base array of styles - runMode(cm, line.text, cm.doc.mode, state, function(end, style) { - st.push(end, style); - }, lineClasses, forceToEnd); - - // Run overlays, adjust style array. - for (var o = 0; o < cm.state.overlays.length; ++o) { - var overlay = cm.state.overlays[o], i = 1, at = 0; - runMode(cm, line.text, overlay.mode, true, function(end, style) { - var start = i; - // Ensure there's a token end at the current position, and that i points at it - while (at < end) { - var i_end = st[i]; - if (i_end > end) - st.splice(i, 1, end, st[i+1], i_end); - i += 2; - at = Math.min(end, i_end); - } - if (!style) return; - if (overlay.opaque) { - st.splice(start, i - start, end, "cm-overlay " + style); - i = start + 2; - } else { - for (; start < i; start += 2) { - var cur = st[start+1]; - st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; - } - } - }, lineClasses); - } - - return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}; - } - - function getLineStyles(cm, line, updateFrontier) { - if (!line.styles || line.styles[0] != cm.state.modeGen) { - var state = getStateBefore(cm, lineNo(line)); - var result = highlightLine(cm, line, line.text.length > cm.options.maxHighlightLength ? copyState(cm.doc.mode, state) : state); - line.stateAfter = state; - line.styles = result.styles; - if (result.classes) line.styleClasses = result.classes; - else if (line.styleClasses) line.styleClasses = null; - if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; - } - return line.styles; - } - - // Lightweight form of highlight -- proceed over this line and - // update state, but don't save a style array. Used for lines that - // aren't currently visible. - function processLine(cm, text, state, startAt) { - var mode = cm.doc.mode; - var stream = new StringStream(text, cm.options.tabSize); - stream.start = stream.pos = startAt || 0; - if (text == "") callBlankLine(mode, state); - while (!stream.eol()) { - readToken(mode, stream, state); - stream.start = stream.pos; - } - } - - // Convert a style as returned by a mode (either null, or a string - // containing one or more styles) to a CSS style. This is cached, - // and also looks for line-wide styles. - var styleToClassCache = {}, styleToClassCacheWithMode = {}; - function interpretTokenStyle(style, options) { - if (!style || /^\s*$/.test(style)) return null; - var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; - return cache[style] || - (cache[style] = style.replace(/\S+/g, "cm-$&")); - } - - // Render the DOM representation of the text of a line. Also builds - // up a 'line map', which points at the DOM nodes that represent - // specific stretches of text, and is used by the measuring code. - // The returned object contains the DOM node, this map, and - // information about line-wide styles that were set by the mode. - function buildLineContent(cm, lineView) { - // The padding-right forces the element to have a 'border', which - // is needed on Webkit to be able to get line-level bounding - // rectangles for it (in measureChar). - var content = elt("span", null, null, webkit ? "padding-right: .1px" : null); - var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: content, - col: 0, pos: 0, cm: cm, - splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; - lineView.measure = {}; - - // Iterate over the logical lines that make up this visual line. - for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { - var line = i ? lineView.rest[i - 1] : lineView.line, order; - builder.pos = 0; - builder.addToken = buildToken; - // Optionally wire in some hacks into the token-rendering - // algorithm, to deal with browser quirks. - if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) - builder.addToken = buildTokenBadBidi(builder.addToken, order); - builder.map = []; - var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); - insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); - if (line.styleClasses) { - if (line.styleClasses.bgClass) - builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); - if (line.styleClasses.textClass) - builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); - } - - // Ensure at least a single node is present, for measuring. - if (builder.map.length == 0) - builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); - - // Store the map and a cache object for the current logical line - if (i == 0) { - lineView.measure.map = builder.map; - lineView.measure.cache = {}; - } else { - (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map); - (lineView.measure.caches || (lineView.measure.caches = [])).push({}); - } - } - - // See issue #2901 - if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className)) - builder.content.className = "cm-tab-wrap-hack"; - - signal(cm, "renderLine", cm, lineView.line, builder.pre); - if (builder.pre.className) - builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); - - return builder; - } - - function defaultSpecialCharPlaceholder(ch) { - var token = elt("span", "\u2022", "cm-invalidchar"); - token.title = "\\u" + ch.charCodeAt(0).toString(16); - token.setAttribute("aria-label", token.title); - return token; - } - - // Build up the DOM representation for a single token, and add it to - // the line map. Takes care to render special characters separately. - function buildToken(builder, text, style, startStyle, endStyle, title, css) { - if (!text) return; - var displayText = builder.splitSpaces ? text.replace(/ {3,}/g, splitSpaces) : text; - var special = builder.cm.state.specialChars, mustWrap = false; - if (!special.test(text)) { - builder.col += text.length; - var content = document.createTextNode(displayText); - builder.map.push(builder.pos, builder.pos + text.length, content); - if (ie && ie_version < 9) mustWrap = true; - builder.pos += text.length; - } else { - var content = document.createDocumentFragment(), pos = 0; - while (true) { - special.lastIndex = pos; - var m = special.exec(text); - var skipped = m ? m.index - pos : text.length - pos; - if (skipped) { - var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); - if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); - else content.appendChild(txt); - builder.map.push(builder.pos, builder.pos + skipped, txt); - builder.col += skipped; - builder.pos += skipped; - } - if (!m) break; - pos += skipped + 1; - if (m[0] == "\t") { - var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; - var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); - txt.setAttribute("role", "presentation"); - txt.setAttribute("cm-text", "\t"); - builder.col += tabWidth; - } else if (m[0] == "\r" || m[0] == "\n") { - var txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")); - txt.setAttribute("cm-text", m[0]); - builder.col += 1; - } else { - var txt = builder.cm.options.specialCharPlaceholder(m[0]); - txt.setAttribute("cm-text", m[0]); - if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); - else content.appendChild(txt); - builder.col += 1; - } - builder.map.push(builder.pos, builder.pos + 1, txt); - builder.pos++; - } - } - if (style || startStyle || endStyle || mustWrap || css) { - var fullStyle = style || ""; - if (startStyle) fullStyle += startStyle; - if (endStyle) fullStyle += endStyle; - var token = elt("span", [content], fullStyle, css); - if (title) token.title = title; - return builder.content.appendChild(token); - } - builder.content.appendChild(content); - } - - function splitSpaces(old) { - var out = " "; - for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; - out += " "; - return out; - } - - // Work around nonsense dimensions being reported for stretches of - // right-to-left text. - function buildTokenBadBidi(inner, order) { - return function(builder, text, style, startStyle, endStyle, title, css) { - style = style ? style + " cm-force-border" : "cm-force-border"; - var start = builder.pos, end = start + text.length; - for (;;) { - // Find the part that overlaps with the start of this text - for (var i = 0; i < order.length; i++) { - var part = order[i]; - if (part.to > start && part.from <= start) break; - } - if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css); - inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css); - startStyle = null; - text = text.slice(part.to - start); - start = part.to; - } - }; - } - - function buildCollapsedSpan(builder, size, marker, ignoreWidget) { - var widget = !ignoreWidget && marker.widgetNode; - if (widget) builder.map.push(builder.pos, builder.pos + size, widget); - if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { - if (!widget) - widget = builder.content.appendChild(document.createElement("span")); - widget.setAttribute("cm-marker", marker.id); - } - if (widget) { - builder.cm.display.input.setUneditable(widget); - builder.content.appendChild(widget); - } - builder.pos += size; - } - - // Outputs a number of spans to make up a line, taking highlighting - // and marked text into account. - function insertLineContent(line, builder, styles) { - var spans = line.markedSpans, allText = line.text, at = 0; - if (!spans) { - for (var i = 1; i < styles.length; i+=2) - builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)); - return; - } - - var len = allText.length, pos = 0, i = 1, text = "", style, css; - var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; - for (;;) { - if (nextChange == pos) { // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = title = css = ""; - collapsed = null; nextChange = Infinity; - var foundBookmarks = [], endStyles - for (var j = 0; j < spans.length; ++j) { - var sp = spans[j], m = sp.marker; - if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { - foundBookmarks.push(m); - } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { - if (sp.to != null && sp.to != pos && nextChange > sp.to) { - nextChange = sp.to; - spanEndStyle = ""; - } - if (m.className) spanStyle += " " + m.className; - if (m.css) css = (css ? css + ";" : "") + m.css; - if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; - if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to) - if (m.title && !title) title = m.title; - if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) - collapsed = sp; - } else if (sp.from > pos && nextChange > sp.from) { - nextChange = sp.from; - } - } - if (endStyles) for (var j = 0; j < endStyles.length; j += 2) - if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j] - - if (!collapsed || collapsed.from == pos) for (var j = 0; j < foundBookmarks.length; ++j) - buildCollapsedSpan(builder, 0, foundBookmarks[j]); - if (collapsed && (collapsed.from || 0) == pos) { - buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, - collapsed.marker, collapsed.from == null); - if (collapsed.to == null) return; - if (collapsed.to == pos) collapsed = false; - } - } - if (pos >= len) break; - - var upto = Math.min(len, nextChange); - while (true) { - if (text) { - var end = pos + text.length; - if (!collapsed) { - var tokenText = end > upto ? text.slice(0, upto - pos) : text; - builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, - spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css); - } - if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} - pos = end; - spanStartStyle = ""; - } - text = allText.slice(at, at = styles[i++]); - style = interpretTokenStyle(styles[i++], builder.cm.options); - } - } - } - - // DOCUMENT DATA STRUCTURE - - // By default, updates that start and end at the beginning of a line - // are treated specially, in order to make the association of line - // widgets and marker elements with the text behave more intuitive. - function isWholeLineUpdate(doc, change) { - return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && - (!doc.cm || doc.cm.options.wholeLineUpdateBefore); - } - - // Perform a change on the document data structure. - function updateDoc(doc, change, markedSpans, estimateHeight) { - function spansFor(n) {return markedSpans ? markedSpans[n] : null;} - function update(line, text, spans) { - updateLine(line, text, spans, estimateHeight); - signalLater(line, "change", line, change); - } - function linesFor(start, end) { - for (var i = start, result = []; i < end; ++i) - result.push(new Line(text[i], spansFor(i), estimateHeight)); - return result; - } - - var from = change.from, to = change.to, text = change.text; - var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); - var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; - - // Adjust the line structure - if (change.full) { - doc.insert(0, linesFor(0, text.length)); - doc.remove(text.length, doc.size - text.length); - } else if (isWholeLineUpdate(doc, change)) { - // This is a whole-line replace. Treated specially to make - // sure line objects move the way they are supposed to. - var added = linesFor(0, text.length - 1); - update(lastLine, lastLine.text, lastSpans); - if (nlines) doc.remove(from.line, nlines); - if (added.length) doc.insert(from.line, added); - } else if (firstLine == lastLine) { - if (text.length == 1) { - update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); - } else { - var added = linesFor(1, text.length - 1); - added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); - update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); - doc.insert(from.line + 1, added); - } - } else if (text.length == 1) { - update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); - doc.remove(from.line + 1, nlines); - } else { - update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); - update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); - var added = linesFor(1, text.length - 1); - if (nlines > 1) doc.remove(from.line + 1, nlines - 1); - doc.insert(from.line + 1, added); - } - - signalLater(doc, "change", doc, change); - } - - // The document is represented as a BTree consisting of leaves, with - // chunk of lines in them, and branches, with up to ten leaves or - // other branch nodes below them. The top node is always a branch - // node, and is the document object itself (meaning it has - // additional methods and properties). - // - // All nodes have parent links. The tree is used both to go from - // line numbers to line objects, and to go from objects to numbers. - // It also indexes by height, and is used to convert between height - // and line object, and to find the total height of the document. - // - // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html - - function LeafChunk(lines) { - this.lines = lines; - this.parent = null; - for (var i = 0, height = 0; i < lines.length; ++i) { - lines[i].parent = this; - height += lines[i].height; - } - this.height = height; - } - - LeafChunk.prototype = { - chunkSize: function() { return this.lines.length; }, - // Remove the n lines at offset 'at'. - removeInner: function(at, n) { - for (var i = at, e = at + n; i < e; ++i) { - var line = this.lines[i]; - this.height -= line.height; - cleanUpLine(line); - signalLater(line, "delete"); - } - this.lines.splice(at, n); - }, - // Helper used to collapse a small branch into a single leaf. - collapse: function(lines) { - lines.push.apply(lines, this.lines); - }, - // Insert the given array of lines at offset 'at', count them as - // having the given height. - insertInner: function(at, lines, height) { - this.height += height; - this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); - for (var i = 0; i < lines.length; ++i) lines[i].parent = this; - }, - // Used to iterate over a part of the tree. - iterN: function(at, n, op) { - for (var e = at + n; at < e; ++at) - if (op(this.lines[at])) return true; - } - }; - - function BranchChunk(children) { - this.children = children; - var size = 0, height = 0; - for (var i = 0; i < children.length; ++i) { - var ch = children[i]; - size += ch.chunkSize(); height += ch.height; - ch.parent = this; - } - this.size = size; - this.height = height; - this.parent = null; - } - - BranchChunk.prototype = { - chunkSize: function() { return this.size; }, - removeInner: function(at, n) { - this.size -= n; - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var rm = Math.min(n, sz - at), oldHeight = child.height; - child.removeInner(at, rm); - this.height -= oldHeight - child.height; - if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } - if ((n -= rm) == 0) break; - at = 0; - } else at -= sz; - } - // If the result is smaller than 25 lines, ensure that it is a - // single leaf node. - if (this.size - n < 25 && - (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { - var lines = []; - this.collapse(lines); - this.children = [new LeafChunk(lines)]; - this.children[0].parent = this; - } - }, - collapse: function(lines) { - for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines); - }, - insertInner: function(at, lines, height) { - this.size += lines.length; - this.height += height; - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at <= sz) { - child.insertInner(at, lines, height); - if (child.lines && child.lines.length > 50) { - while (child.lines.length > 50) { - var spilled = child.lines.splice(child.lines.length - 25, 25); - var newleaf = new LeafChunk(spilled); - child.height -= newleaf.height; - this.children.splice(i + 1, 0, newleaf); - newleaf.parent = this; - } - this.maybeSpill(); - } - break; - } - at -= sz; - } - }, - // When a node has grown, check whether it should be split. - maybeSpill: function() { - if (this.children.length <= 10) return; - var me = this; - do { - var spilled = me.children.splice(me.children.length - 5, 5); - var sibling = new BranchChunk(spilled); - if (!me.parent) { // Become the parent node - var copy = new BranchChunk(me.children); - copy.parent = me; - me.children = [copy, sibling]; - me = copy; - } else { - me.size -= sibling.size; - me.height -= sibling.height; - var myIndex = indexOf(me.parent.children, me); - me.parent.children.splice(myIndex + 1, 0, sibling); - } - sibling.parent = me.parent; - } while (me.children.length > 10); - me.parent.maybeSpill(); - }, - iterN: function(at, n, op) { - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var used = Math.min(n, sz - at); - if (child.iterN(at, used, op)) return true; - if ((n -= used) == 0) break; - at = 0; - } else at -= sz; - } - } - }; - - var nextDocId = 0; - var Doc = CodeMirror.Doc = function(text, mode, firstLine, lineSep) { - if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep); - if (firstLine == null) firstLine = 0; - - BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); - this.first = firstLine; - this.scrollTop = this.scrollLeft = 0; - this.cantEdit = false; - this.cleanGeneration = 1; - this.frontier = firstLine; - var start = Pos(firstLine, 0); - this.sel = simpleSelection(start); - this.history = new History(null); - this.id = ++nextDocId; - this.modeOption = mode; - this.lineSep = lineSep; - this.extend = false; - - if (typeof text == "string") text = this.splitLines(text); - updateDoc(this, {from: start, to: start, text: text}); - setSelection(this, simpleSelection(start), sel_dontScroll); - }; - - Doc.prototype = createObj(BranchChunk.prototype, { - constructor: Doc, - // Iterate over the document. Supports two forms -- with only one - // argument, it calls that for each line in the document. With - // three, it iterates over the range given by the first two (with - // the second being non-inclusive). - iter: function(from, to, op) { - if (op) this.iterN(from - this.first, to - from, op); - else this.iterN(this.first, this.first + this.size, from); - }, - - // Non-public interface for adding and removing lines. - insert: function(at, lines) { - var height = 0; - for (var i = 0; i < lines.length; ++i) height += lines[i].height; - this.insertInner(at - this.first, lines, height); - }, - remove: function(at, n) { this.removeInner(at - this.first, n); }, - - // From here, the methods are part of the public interface. Most - // are also available from CodeMirror (editor) instances. - - getValue: function(lineSep) { - var lines = getLines(this, this.first, this.first + this.size); - if (lineSep === false) return lines; - return lines.join(lineSep || this.lineSeparator()); - }, - setValue: docMethodOp(function(code) { - var top = Pos(this.first, 0), last = this.first + this.size - 1; - makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), - text: this.splitLines(code), origin: "setValue", full: true}, true); - setSelection(this, simpleSelection(top)); - }), - replaceRange: function(code, from, to, origin) { - from = clipPos(this, from); - to = to ? clipPos(this, to) : from; - replaceRange(this, code, from, to, origin); - }, - getRange: function(from, to, lineSep) { - var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); - if (lineSep === false) return lines; - return lines.join(lineSep || this.lineSeparator()); - }, - - getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, - - getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, - getLineNumber: function(line) {return lineNo(line);}, - - getLineHandleVisualStart: function(line) { - if (typeof line == "number") line = getLine(this, line); - return visualLine(line); - }, - - lineCount: function() {return this.size;}, - firstLine: function() {return this.first;}, - lastLine: function() {return this.first + this.size - 1;}, - - clipPos: function(pos) {return clipPos(this, pos);}, - - getCursor: function(start) { - var range = this.sel.primary(), pos; - if (start == null || start == "head") pos = range.head; - else if (start == "anchor") pos = range.anchor; - else if (start == "end" || start == "to" || start === false) pos = range.to(); - else pos = range.from(); - return pos; - }, - listSelections: function() { return this.sel.ranges; }, - somethingSelected: function() {return this.sel.somethingSelected();}, - - setCursor: docMethodOp(function(line, ch, options) { - setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); - }), - setSelection: docMethodOp(function(anchor, head, options) { - setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); - }), - extendSelection: docMethodOp(function(head, other, options) { - extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); - }), - extendSelections: docMethodOp(function(heads, options) { - extendSelections(this, clipPosArray(this, heads), options); - }), - extendSelectionsBy: docMethodOp(function(f, options) { - var heads = map(this.sel.ranges, f); - extendSelections(this, clipPosArray(this, heads), options); - }), - setSelections: docMethodOp(function(ranges, primary, options) { - if (!ranges.length) return; - for (var i = 0, out = []; i < ranges.length; i++) - out[i] = new Range(clipPos(this, ranges[i].anchor), - clipPos(this, ranges[i].head)); - if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex); - setSelection(this, normalizeSelection(out, primary), options); - }), - addSelection: docMethodOp(function(anchor, head, options) { - var ranges = this.sel.ranges.slice(0); - ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); - setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); - }), - - getSelection: function(lineSep) { - var ranges = this.sel.ranges, lines; - for (var i = 0; i < ranges.length; i++) { - var sel = getBetween(this, ranges[i].from(), ranges[i].to()); - lines = lines ? lines.concat(sel) : sel; - } - if (lineSep === false) return lines; - else return lines.join(lineSep || this.lineSeparator()); - }, - getSelections: function(lineSep) { - var parts = [], ranges = this.sel.ranges; - for (var i = 0; i < ranges.length; i++) { - var sel = getBetween(this, ranges[i].from(), ranges[i].to()); - if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator()); - parts[i] = sel; - } - return parts; - }, - replaceSelection: function(code, collapse, origin) { - var dup = []; - for (var i = 0; i < this.sel.ranges.length; i++) - dup[i] = code; - this.replaceSelections(dup, collapse, origin || "+input"); - }, - replaceSelections: docMethodOp(function(code, collapse, origin) { - var changes = [], sel = this.sel; - for (var i = 0; i < sel.ranges.length; i++) { - var range = sel.ranges[i]; - changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin}; - } - var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); - for (var i = changes.length - 1; i >= 0; i--) - makeChange(this, changes[i]); - if (newSel) setSelectionReplaceHistory(this, newSel); - else if (this.cm) ensureCursorVisible(this.cm); - }), - undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), - redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), - undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), - redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), - - setExtending: function(val) {this.extend = val;}, - getExtending: function() {return this.extend;}, - - historySize: function() { - var hist = this.history, done = 0, undone = 0; - for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done; - for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone; - return {undo: done, redo: undone}; - }, - clearHistory: function() {this.history = new History(this.history.maxGeneration);}, - - markClean: function() { - this.cleanGeneration = this.changeGeneration(true); - }, - changeGeneration: function(forceSplit) { - if (forceSplit) - this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; - return this.history.generation; - }, - isClean: function (gen) { - return this.history.generation == (gen || this.cleanGeneration); - }, - - getHistory: function() { - return {done: copyHistoryArray(this.history.done), - undone: copyHistoryArray(this.history.undone)}; - }, - setHistory: function(histData) { - var hist = this.history = new History(this.history.maxGeneration); - hist.done = copyHistoryArray(histData.done.slice(0), null, true); - hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); - }, - - addLineClass: docMethodOp(function(handle, where, cls) { - return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { - var prop = where == "text" ? "textClass" - : where == "background" ? "bgClass" - : where == "gutter" ? "gutterClass" : "wrapClass"; - if (!line[prop]) line[prop] = cls; - else if (classTest(cls).test(line[prop])) return false; - else line[prop] += " " + cls; - return true; - }); - }), - removeLineClass: docMethodOp(function(handle, where, cls) { - return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { - var prop = where == "text" ? "textClass" - : where == "background" ? "bgClass" - : where == "gutter" ? "gutterClass" : "wrapClass"; - var cur = line[prop]; - if (!cur) return false; - else if (cls == null) line[prop] = null; - else { - var found = cur.match(classTest(cls)); - if (!found) return false; - var end = found.index + found[0].length; - line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; - } - return true; - }); - }), - - addLineWidget: docMethodOp(function(handle, node, options) { - return addLineWidget(this, handle, node, options); - }), - removeLineWidget: function(widget) { widget.clear(); }, - - markText: function(from, to, options) { - return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range"); - }, - setBookmark: function(pos, options) { - var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), - insertLeft: options && options.insertLeft, - clearWhenEmpty: false, shared: options && options.shared, - handleMouseEvents: options && options.handleMouseEvents}; - pos = clipPos(this, pos); - return markText(this, pos, pos, realOpts, "bookmark"); - }, - findMarksAt: function(pos) { - pos = clipPos(this, pos); - var markers = [], spans = getLine(this, pos.line).markedSpans; - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if ((span.from == null || span.from <= pos.ch) && - (span.to == null || span.to >= pos.ch)) - markers.push(span.marker.parent || span.marker); - } - return markers; - }, - findMarks: function(from, to, filter) { - from = clipPos(this, from); to = clipPos(this, to); - var found = [], lineNo = from.line; - this.iter(from.line, to.line + 1, function(line) { - var spans = line.markedSpans; - if (spans) for (var i = 0; i < spans.length; i++) { - var span = spans[i]; - if (!(lineNo == from.line && from.ch > span.to || - span.from == null && lineNo != from.line|| - lineNo == to.line && span.from > to.ch) && - (!filter || filter(span.marker))) - found.push(span.marker.parent || span.marker); - } - ++lineNo; - }); - return found; - }, - getAllMarks: function() { - var markers = []; - this.iter(function(line) { - var sps = line.markedSpans; - if (sps) for (var i = 0; i < sps.length; ++i) - if (sps[i].from != null) markers.push(sps[i].marker); - }); - return markers; - }, - - posFromIndex: function(off) { - var ch, lineNo = this.first; - this.iter(function(line) { - var sz = line.text.length + 1; - if (sz > off) { ch = off; return true; } - off -= sz; - ++lineNo; - }); - return clipPos(this, Pos(lineNo, ch)); - }, - indexFromPos: function (coords) { - coords = clipPos(this, coords); - var index = coords.ch; - if (coords.line < this.first || coords.ch < 0) return 0; - this.iter(this.first, coords.line, function (line) { - index += line.text.length + 1; - }); - return index; - }, - - copy: function(copyHistory) { - var doc = new Doc(getLines(this, this.first, this.first + this.size), - this.modeOption, this.first, this.lineSep); - doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; - doc.sel = this.sel; - doc.extend = false; - if (copyHistory) { - doc.history.undoDepth = this.history.undoDepth; - doc.setHistory(this.getHistory()); - } - return doc; - }, - - linkedDoc: function(options) { - if (!options) options = {}; - var from = this.first, to = this.first + this.size; - if (options.from != null && options.from > from) from = options.from; - if (options.to != null && options.to < to) to = options.to; - var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep); - if (options.sharedHist) copy.history = this.history; - (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); - copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; - copySharedMarkers(copy, findSharedMarkers(this)); - return copy; - }, - unlinkDoc: function(other) { - if (other instanceof CodeMirror) other = other.doc; - if (this.linked) for (var i = 0; i < this.linked.length; ++i) { - var link = this.linked[i]; - if (link.doc != other) continue; - this.linked.splice(i, 1); - other.unlinkDoc(this); - detachSharedMarkers(findSharedMarkers(this)); - break; - } - // If the histories were shared, split them again - if (other.history == this.history) { - var splitIds = [other.id]; - linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); - other.history = new History(null); - other.history.done = copyHistoryArray(this.history.done, splitIds); - other.history.undone = copyHistoryArray(this.history.undone, splitIds); - } - }, - iterLinkedDocs: function(f) {linkedDocs(this, f);}, - - getMode: function() {return this.mode;}, - getEditor: function() {return this.cm;}, - - splitLines: function(str) { - if (this.lineSep) return str.split(this.lineSep); - return splitLinesAuto(str); - }, - lineSeparator: function() { return this.lineSep || "\n"; } - }); - - // Public alias. - Doc.prototype.eachLine = Doc.prototype.iter; - - // Set up methods on CodeMirror's prototype to redirect to the editor's document. - var dontDelegate = "iter insert remove copy getEditor constructor".split(" "); - for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) - CodeMirror.prototype[prop] = (function(method) { - return function() {return method.apply(this.doc, arguments);}; - })(Doc.prototype[prop]); - - eventMixin(Doc); - - // Call f for all linked documents. - function linkedDocs(doc, f, sharedHistOnly) { - function propagate(doc, skip, sharedHist) { - if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { - var rel = doc.linked[i]; - if (rel.doc == skip) continue; - var shared = sharedHist && rel.sharedHist; - if (sharedHistOnly && !shared) continue; - f(rel.doc, shared); - propagate(rel.doc, doc, shared); - } - } - propagate(doc, null, true); - } - - // Attach a document to an editor. - function attachDoc(cm, doc) { - if (doc.cm) throw new Error("This document is already in use."); - cm.doc = doc; - doc.cm = cm; - estimateLineHeights(cm); - loadMode(cm); - if (!cm.options.lineWrapping) findMaxLine(cm); - cm.options.mode = doc.modeOption; - regChange(cm); - } - - // LINE UTILITIES - - // Find the line object corresponding to the given line number. - function getLine(doc, n) { - n -= doc.first; - if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document."); - for (var chunk = doc; !chunk.lines;) { - for (var i = 0;; ++i) { - var child = chunk.children[i], sz = child.chunkSize(); - if (n < sz) { chunk = child; break; } - n -= sz; - } - } - return chunk.lines[n]; - } - - // Get the part of a document between two positions, as an array of - // strings. - function getBetween(doc, start, end) { - var out = [], n = start.line; - doc.iter(start.line, end.line + 1, function(line) { - var text = line.text; - if (n == end.line) text = text.slice(0, end.ch); - if (n == start.line) text = text.slice(start.ch); - out.push(text); - ++n; - }); - return out; - } - // Get the lines between from and to, as array of strings. - function getLines(doc, from, to) { - var out = []; - doc.iter(from, to, function(line) { out.push(line.text); }); - return out; - } - - // Update the height of a line, propagating the height change - // upwards to parent nodes. - function updateLineHeight(line, height) { - var diff = height - line.height; - if (diff) for (var n = line; n; n = n.parent) n.height += diff; - } - - // Given a line object, find its line number by walking up through - // its parent links. - function lineNo(line) { - if (line.parent == null) return null; - var cur = line.parent, no = indexOf(cur.lines, line); - for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { - for (var i = 0;; ++i) { - if (chunk.children[i] == cur) break; - no += chunk.children[i].chunkSize(); - } - } - return no + cur.first; - } - - // Find the line at the given vertical position, using the height - // information in the document tree. - function lineAtHeight(chunk, h) { - var n = chunk.first; - outer: do { - for (var i = 0; i < chunk.children.length; ++i) { - var child = chunk.children[i], ch = child.height; - if (h < ch) { chunk = child; continue outer; } - h -= ch; - n += child.chunkSize(); - } - return n; - } while (!chunk.lines); - for (var i = 0; i < chunk.lines.length; ++i) { - var line = chunk.lines[i], lh = line.height; - if (h < lh) break; - h -= lh; - } - return n + i; - } - - - // Find the height above the given line. - function heightAtLine(lineObj) { - lineObj = visualLine(lineObj); - - var h = 0, chunk = lineObj.parent; - for (var i = 0; i < chunk.lines.length; ++i) { - var line = chunk.lines[i]; - if (line == lineObj) break; - else h += line.height; - } - for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { - for (var i = 0; i < p.children.length; ++i) { - var cur = p.children[i]; - if (cur == chunk) break; - else h += cur.height; - } - } - return h; - } - - // Get the bidi ordering for the given line (and cache it). Returns - // false for lines that are fully left-to-right, and an array of - // BidiSpan objects otherwise. - function getOrder(line) { - var order = line.order; - if (order == null) order = line.order = bidiOrdering(line.text); - return order; - } - - // HISTORY - - function History(startGen) { - // Arrays of change events and selections. Doing something adds an - // event to done and clears undo. Undoing moves events from done - // to undone, redoing moves them in the other direction. - this.done = []; this.undone = []; - this.undoDepth = Infinity; - // Used to track when changes can be merged into a single undo - // event - this.lastModTime = this.lastSelTime = 0; - this.lastOp = this.lastSelOp = null; - this.lastOrigin = this.lastSelOrigin = null; - // Used by the isClean() method - this.generation = this.maxGeneration = startGen || 1; - } - - // Create a history change event from an updateDoc-style change - // object. - function historyChangeFromChange(doc, change) { - var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; - attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); - linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); - return histChange; - } - - // Pop all selection events off the end of a history array. Stop at - // a change event. - function clearSelectionEvents(array) { - while (array.length) { - var last = lst(array); - if (last.ranges) array.pop(); - else break; - } - } - - // Find the top change event in the history. Pop off selection - // events that are in the way. - function lastChangeEvent(hist, force) { - if (force) { - clearSelectionEvents(hist.done); - return lst(hist.done); - } else if (hist.done.length && !lst(hist.done).ranges) { - return lst(hist.done); - } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { - hist.done.pop(); - return lst(hist.done); - } - } - - // Register a change in the history. Merges changes that are within - // a single operation, ore are close together with an origin that - // allows merging (starting with "+") into a single event. - function addChangeToHistory(doc, change, selAfter, opId) { - var hist = doc.history; - hist.undone.length = 0; - var time = +new Date, cur; - - if ((hist.lastOp == opId || - hist.lastOrigin == change.origin && change.origin && - ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || - change.origin.charAt(0) == "*")) && - (cur = lastChangeEvent(hist, hist.lastOp == opId))) { - // Merge this change into the last event - var last = lst(cur.changes); - if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { - // Optimized case for simple insertion -- don't want to add - // new changesets for every character typed - last.to = changeEnd(change); - } else { - // Add new sub-event - cur.changes.push(historyChangeFromChange(doc, change)); - } - } else { - // Can not be merged, start a new event. - var before = lst(hist.done); - if (!before || !before.ranges) - pushSelectionToHistory(doc.sel, hist.done); - cur = {changes: [historyChangeFromChange(doc, change)], - generation: hist.generation}; - hist.done.push(cur); - while (hist.done.length > hist.undoDepth) { - hist.done.shift(); - if (!hist.done[0].ranges) hist.done.shift(); - } - } - hist.done.push(selAfter); - hist.generation = ++hist.maxGeneration; - hist.lastModTime = hist.lastSelTime = time; - hist.lastOp = hist.lastSelOp = opId; - hist.lastOrigin = hist.lastSelOrigin = change.origin; - - if (!last) signal(doc, "historyAdded"); - } - - function selectionEventCanBeMerged(doc, origin, prev, sel) { - var ch = origin.charAt(0); - return ch == "*" || - ch == "+" && - prev.ranges.length == sel.ranges.length && - prev.somethingSelected() == sel.somethingSelected() && - new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500); - } - - // Called whenever the selection changes, sets the new selection as - // the pending selection in the history, and pushes the old pending - // selection into the 'done' array when it was significantly - // different (in number of selected ranges, emptiness, or time). - function addSelectionToHistory(doc, sel, opId, options) { - var hist = doc.history, origin = options && options.origin; - - // A new event is started when the previous origin does not match - // the current, or the origins don't allow matching. Origins - // starting with * are always merged, those starting with + are - // merged when similar and close together in time. - if (opId == hist.lastSelOp || - (origin && hist.lastSelOrigin == origin && - (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || - selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) - hist.done[hist.done.length - 1] = sel; - else - pushSelectionToHistory(sel, hist.done); - - hist.lastSelTime = +new Date; - hist.lastSelOrigin = origin; - hist.lastSelOp = opId; - if (options && options.clearRedo !== false) - clearSelectionEvents(hist.undone); - } - - function pushSelectionToHistory(sel, dest) { - var top = lst(dest); - if (!(top && top.ranges && top.equals(sel))) - dest.push(sel); - } - - // Used to store marked span information in the history. - function attachLocalSpans(doc, change, from, to) { - var existing = change["spans_" + doc.id], n = 0; - doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { - if (line.markedSpans) - (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; - ++n; - }); - } - - // When un/re-doing restores text containing marked spans, those - // that have been explicitly cleared should not be restored. - function removeClearedSpans(spans) { - if (!spans) return null; - for (var i = 0, out; i < spans.length; ++i) { - if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } - else if (out) out.push(spans[i]); - } - return !out ? spans : out.length ? out : null; - } - - // Retrieve and filter the old marked spans stored in a change event. - function getOldSpans(doc, change) { - var found = change["spans_" + doc.id]; - if (!found) return null; - for (var i = 0, nw = []; i < change.text.length; ++i) - nw.push(removeClearedSpans(found[i])); - return nw; - } - - // Used both to provide a JSON-safe object in .getHistory, and, when - // detaching a document, to split the history in two - function copyHistoryArray(events, newGroup, instantiateSel) { - for (var i = 0, copy = []; i < events.length; ++i) { - var event = events[i]; - if (event.ranges) { - copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); - continue; - } - var changes = event.changes, newChanges = []; - copy.push({changes: newChanges}); - for (var j = 0; j < changes.length; ++j) { - var change = changes[j], m; - newChanges.push({from: change.from, to: change.to, text: change.text}); - if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { - if (indexOf(newGroup, Number(m[1])) > -1) { - lst(newChanges)[prop] = change[prop]; - delete change[prop]; - } - } - } - } - return copy; - } - - // Rebasing/resetting history to deal with externally-sourced changes - - function rebaseHistSelSingle(pos, from, to, diff) { - if (to < pos.line) { - pos.line += diff; - } else if (from < pos.line) { - pos.line = from; - pos.ch = 0; - } - } - - // Tries to rebase an array of history events given a change in the - // document. If the change touches the same lines as the event, the - // event, and everything 'behind' it, is discarded. If the change is - // before the event, the event's positions are updated. Uses a - // copy-on-write scheme for the positions, to avoid having to - // reallocate them all on every rebase, but also avoid problems with - // shared position objects being unsafely updated. - function rebaseHistArray(array, from, to, diff) { - for (var i = 0; i < array.length; ++i) { - var sub = array[i], ok = true; - if (sub.ranges) { - if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } - for (var j = 0; j < sub.ranges.length; j++) { - rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); - rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); - } - continue; - } - for (var j = 0; j < sub.changes.length; ++j) { - var cur = sub.changes[j]; - if (to < cur.from.line) { - cur.from = Pos(cur.from.line + diff, cur.from.ch); - cur.to = Pos(cur.to.line + diff, cur.to.ch); - } else if (from <= cur.to.line) { - ok = false; - break; - } - } - if (!ok) { - array.splice(0, i + 1); - i = 0; - } - } - } - - function rebaseHist(hist, change) { - var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; - rebaseHistArray(hist.done, from, to, diff); - rebaseHistArray(hist.undone, from, to, diff); - } - - // EVENT UTILITIES - - // Due to the fact that we still support jurassic IE versions, some - // compatibility wrappers are needed. - - var e_preventDefault = CodeMirror.e_preventDefault = function(e) { - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - }; - var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) { - if (e.stopPropagation) e.stopPropagation(); - else e.cancelBubble = true; - }; - function e_defaultPrevented(e) { - return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; - } - var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);}; - - function e_target(e) {return e.target || e.srcElement;} - function e_button(e) { - var b = e.which; - if (b == null) { - if (e.button & 1) b = 1; - else if (e.button & 2) b = 3; - else if (e.button & 4) b = 2; - } - if (mac && e.ctrlKey && b == 1) b = 3; - return b; - } - - // EVENT HANDLING - - // Lightweight event framework. on/off also work on DOM nodes, - // registering native DOM handlers. - - var on = CodeMirror.on = function(emitter, type, f) { - if (emitter.addEventListener) - emitter.addEventListener(type, f, false); - else if (emitter.attachEvent) - emitter.attachEvent("on" + type, f); - else { - var map = emitter._handlers || (emitter._handlers = {}); - var arr = map[type] || (map[type] = []); - arr.push(f); - } - }; - - var noHandlers = [] - function getHandlers(emitter, type, copy) { - var arr = emitter._handlers && emitter._handlers[type] - if (copy) return arr && arr.length > 0 ? arr.slice() : noHandlers - else return arr || noHandlers - } - - var off = CodeMirror.off = function(emitter, type, f) { - if (emitter.removeEventListener) - emitter.removeEventListener(type, f, false); - else if (emitter.detachEvent) - emitter.detachEvent("on" + type, f); - else { - var handlers = getHandlers(emitter, type, false) - for (var i = 0; i < handlers.length; ++i) - if (handlers[i] == f) { handlers.splice(i, 1); break; } - } - }; - - var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { - var handlers = getHandlers(emitter, type, true) - if (!handlers.length) return; - var args = Array.prototype.slice.call(arguments, 2); - for (var i = 0; i < handlers.length; ++i) handlers[i].apply(null, args); - }; - - var orphanDelayedCallbacks = null; - - // Often, we want to signal events at a point where we are in the - // middle of some work, but don't want the handler to start calling - // other methods on the editor, which might be in an inconsistent - // state or simply not expect any other events to happen. - // signalLater looks whether there are any handlers, and schedules - // them to be executed when the last operation ends, or, if no - // operation is active, when a timeout fires. - function signalLater(emitter, type /*, values...*/) { - var arr = getHandlers(emitter, type, false) - if (!arr.length) return; - var args = Array.prototype.slice.call(arguments, 2), list; - if (operationGroup) { - list = operationGroup.delayedCallbacks; - } else if (orphanDelayedCallbacks) { - list = orphanDelayedCallbacks; - } else { - list = orphanDelayedCallbacks = []; - setTimeout(fireOrphanDelayed, 0); - } - function bnd(f) {return function(){f.apply(null, args);};}; - for (var i = 0; i < arr.length; ++i) - list.push(bnd(arr[i])); - } - - function fireOrphanDelayed() { - var delayed = orphanDelayedCallbacks; - orphanDelayedCallbacks = null; - for (var i = 0; i < delayed.length; ++i) delayed[i](); - } - - // The DOM events that CodeMirror handles can be overridden by - // registering a (non-DOM) handler on the editor for the event name, - // and preventDefault-ing the event in that handler. - function signalDOMEvent(cm, e, override) { - if (typeof e == "string") - e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; - signal(cm, override || e.type, cm, e); - return e_defaultPrevented(e) || e.codemirrorIgnore; - } - - function signalCursorActivity(cm) { - var arr = cm._handlers && cm._handlers.cursorActivity; - if (!arr) return; - var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); - for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) - set.push(arr[i]); - } - - function hasHandler(emitter, type) { - return getHandlers(emitter, type).length > 0 - } - - // Add on and off methods to a constructor's prototype, to make - // registering events on such objects more convenient. - function eventMixin(ctor) { - ctor.prototype.on = function(type, f) {on(this, type, f);}; - ctor.prototype.off = function(type, f) {off(this, type, f);}; - } - - // MISC UTILITIES - - // Number of pixels added to scroller and sizer to hide scrollbar - var scrollerGap = 30; - - // Returned or thrown by various protocols to signal 'I'm not - // handling this'. - var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; - - // Reused option objects for setSelection & friends - var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; - - function Delayed() {this.id = null;} - Delayed.prototype.set = function(ms, f) { - clearTimeout(this.id); - this.id = setTimeout(f, ms); - }; - - // Counts the column offset in a string, taking tabs into account. - // Used mostly to find indentation. - var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) { - if (end == null) { - end = string.search(/[^\s\u00a0]/); - if (end == -1) end = string.length; - } - for (var i = startIndex || 0, n = startValue || 0;;) { - var nextTab = string.indexOf("\t", i); - if (nextTab < 0 || nextTab >= end) - return n + (end - i); - n += nextTab - i; - n += tabSize - (n % tabSize); - i = nextTab + 1; - } - }; - - // The inverse of countColumn -- find the offset that corresponds to - // a particular column. - var findColumn = CodeMirror.findColumn = function(string, goal, tabSize) { - for (var pos = 0, col = 0;;) { - var nextTab = string.indexOf("\t", pos); - if (nextTab == -1) nextTab = string.length; - var skipped = nextTab - pos; - if (nextTab == string.length || col + skipped >= goal) - return pos + Math.min(skipped, goal - col); - col += nextTab - pos; - col += tabSize - (col % tabSize); - pos = nextTab + 1; - if (col >= goal) return pos; - } - } - - var spaceStrs = [""]; - function spaceStr(n) { - while (spaceStrs.length <= n) - spaceStrs.push(lst(spaceStrs) + " "); - return spaceStrs[n]; - } - - function lst(arr) { return arr[arr.length-1]; } - - var selectInput = function(node) { node.select(); }; - if (ios) // Mobile Safari apparently has a bug where select() is broken. - selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; - else if (ie) // Suppress mysterious IE10 errors - selectInput = function(node) { try { node.select(); } catch(_e) {} }; - - function indexOf(array, elt) { - for (var i = 0; i < array.length; ++i) - if (array[i] == elt) return i; - return -1; - } - function map(array, f) { - var out = []; - for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); - return out; - } - - function nothing() {} - - function createObj(base, props) { - var inst; - if (Object.create) { - inst = Object.create(base); - } else { - nothing.prototype = base; - inst = new nothing(); - } - if (props) copyObj(props, inst); - return inst; - }; - - function copyObj(obj, target, overwrite) { - if (!target) target = {}; - for (var prop in obj) - if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) - target[prop] = obj[prop]; - return target; - } - - function bind(f) { - var args = Array.prototype.slice.call(arguments, 1); - return function(){return f.apply(null, args);}; - } - - var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; - var isWordCharBasic = CodeMirror.isWordChar = function(ch) { - return /\w/.test(ch) || ch > "\x80" && - (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); - }; - function isWordChar(ch, helper) { - if (!helper) return isWordCharBasic(ch); - if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; - return helper.test(ch); - } - - function isEmpty(obj) { - for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; - return true; - } - - // Extending unicode characters. A series of a non-extending char + - // any number of extending chars is treated as a single unit as far - // as editing and measuring is concerned. This is not fully correct, - // since some scripts/fonts/browsers also treat other configurations - // of code points as a group. - var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; - function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } - - // DOM UTILITIES - - function elt(tag, content, className, style) { - var e = document.createElement(tag); - if (className) e.className = className; - if (style) e.style.cssText = style; - if (typeof content == "string") e.appendChild(document.createTextNode(content)); - else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); - return e; - } - - var range; - if (document.createRange) range = function(node, start, end, endNode) { - var r = document.createRange(); - r.setEnd(endNode || node, end); - r.setStart(node, start); - return r; - }; - else range = function(node, start, end) { - var r = document.body.createTextRange(); - try { r.moveToElementText(node.parentNode); } - catch(e) { return r; } - r.collapse(true); - r.moveEnd("character", end); - r.moveStart("character", start); - return r; - }; - - function removeChildren(e) { - for (var count = e.childNodes.length; count > 0; --count) - e.removeChild(e.firstChild); - return e; - } - - function removeChildrenAndAdd(parent, e) { - return removeChildren(parent).appendChild(e); - } - - var contains = CodeMirror.contains = function(parent, child) { - if (child.nodeType == 3) // Android browser always returns false when child is a textnode - child = child.parentNode; - if (parent.contains) - return parent.contains(child); - do { - if (child.nodeType == 11) child = child.host; - if (child == parent) return true; - } while (child = child.parentNode); - }; - - function activeElt() { - var activeElement = document.activeElement; - while (activeElement && activeElement.root && activeElement.root.activeElement) - activeElement = activeElement.root.activeElement; - return activeElement; - } - // Older versions of IE throws unspecified error when touching - // document.activeElement in some cases (during loading, in iframe) - if (ie && ie_version < 11) activeElt = function() { - try { return document.activeElement; } - catch(e) { return document.body; } - }; - - function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); } - var rmClass = CodeMirror.rmClass = function(node, cls) { - var current = node.className; - var match = classTest(cls).exec(current); - if (match) { - var after = current.slice(match.index + match[0].length); - node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); - } - }; - var addClass = CodeMirror.addClass = function(node, cls) { - var current = node.className; - if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls; - }; - function joinClasses(a, b) { - var as = a.split(" "); - for (var i = 0; i < as.length; i++) - if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; - return b; - } - - // WINDOW-WIDE EVENTS - - // These must be handled carefully, because naively registering a - // handler for each editor will cause the editors to never be - // garbage collected. - - function forEachCodeMirror(f) { - if (!document.body.getElementsByClassName) return; - var byClass = document.body.getElementsByClassName("CodeMirror"); - for (var i = 0; i < byClass.length; i++) { - var cm = byClass[i].CodeMirror; - if (cm) f(cm); - } - } - - var globalsRegistered = false; - function ensureGlobalHandlers() { - if (globalsRegistered) return; - registerGlobalHandlers(); - globalsRegistered = true; - } - function registerGlobalHandlers() { - // When the window resizes, we need to refresh active editors. - var resizeTimer; - on(window, "resize", function() { - if (resizeTimer == null) resizeTimer = setTimeout(function() { - resizeTimer = null; - forEachCodeMirror(onResize); - }, 100); - }); - // When the window loses focus, we want to show the editor as blurred - on(window, "blur", function() { - forEachCodeMirror(onBlur); - }); - } - - // FEATURE DETECTION - - // Detect drag-and-drop - var dragAndDrop = function() { - // There is *some* kind of drag-and-drop support in IE6-8, but I - // couldn't get it to work yet. - if (ie && ie_version < 9) return false; - var div = elt('div'); - return "draggable" in div || "dragDrop" in div; - }(); - - var zwspSupported; - function zeroWidthElement(measure) { - if (zwspSupported == null) { - var test = elt("span", "\u200b"); - removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); - if (measure.firstChild.offsetHeight != 0) - zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); - } - var node = zwspSupported ? elt("span", "\u200b") : - elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); - node.setAttribute("cm-text", ""); - return node; - } - - // Feature-detect IE's crummy client rect reporting for bidi text - var badBidiRects; - function hasBadBidiRects(measure) { - if (badBidiRects != null) return badBidiRects; - var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); - var r0 = range(txt, 0, 1).getBoundingClientRect(); - if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780) - var r1 = range(txt, 1, 2).getBoundingClientRect(); - return badBidiRects = (r1.right - r0.right < 3); - } - - // See if "".split is the broken IE version, if so, provide an - // alternative way to split lines. - var splitLinesAuto = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { - var pos = 0, result = [], l = string.length; - while (pos <= l) { - var nl = string.indexOf("\n", pos); - if (nl == -1) nl = string.length; - var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); - var rt = line.indexOf("\r"); - if (rt != -1) { - result.push(line.slice(0, rt)); - pos += rt + 1; - } else { - result.push(line); - pos = nl + 1; - } - } - return result; - } : function(string){return string.split(/\r\n?|\n/);}; - - var hasSelection = window.getSelection ? function(te) { - try { return te.selectionStart != te.selectionEnd; } - catch(e) { return false; } - } : function(te) { - try {var range = te.ownerDocument.selection.createRange();} - catch(e) {} - if (!range || range.parentElement() != te) return false; - return range.compareEndPoints("StartToEnd", range) != 0; - }; - - var hasCopyEvent = (function() { - var e = elt("div"); - if ("oncopy" in e) return true; - e.setAttribute("oncopy", "return;"); - return typeof e.oncopy == "function"; - })(); - - var badZoomedRects = null; - function hasBadZoomedRects(measure) { - if (badZoomedRects != null) return badZoomedRects; - var node = removeChildrenAndAdd(measure, elt("span", "x")); - var normal = node.getBoundingClientRect(); - var fromRange = range(node, 0, 1).getBoundingClientRect(); - return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; - } - - // KEY NAMES - - var keyNames = CodeMirror.keyNames = { - 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", - 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", - 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", - 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", - 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete", - 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", - 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", - 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert" - }; - (function() { - // Number keys - for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i); - // Alphabetic keys - for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); - // Function keys - for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; - })(); - - // BIDI HELPERS - - function iterateBidiSections(order, from, to, f) { - if (!order) return f(from, to, "ltr"); - var found = false; - for (var i = 0; i < order.length; ++i) { - var part = order[i]; - if (part.from < to && part.to > from || from == to && part.to == from) { - f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); - found = true; - } - } - if (!found) f(from, to, "ltr"); - } - - function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } - function bidiRight(part) { return part.level % 2 ? part.from : part.to; } - - function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } - function lineRight(line) { - var order = getOrder(line); - if (!order) return line.text.length; - return bidiRight(lst(order)); - } - - function lineStart(cm, lineN) { - var line = getLine(cm.doc, lineN); - var visual = visualLine(line); - if (visual != line) lineN = lineNo(visual); - var order = getOrder(visual); - var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); - return Pos(lineN, ch); - } - function lineEnd(cm, lineN) { - var merged, line = getLine(cm.doc, lineN); - while (merged = collapsedSpanAtEnd(line)) { - line = merged.find(1, true).line; - lineN = null; - } - var order = getOrder(line); - var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); - return Pos(lineN == null ? lineNo(line) : lineN, ch); - } - function lineStartSmart(cm, pos) { - var start = lineStart(cm, pos.line); - var line = getLine(cm.doc, start.line); - var order = getOrder(line); - if (!order || order[0].level == 0) { - var firstNonWS = Math.max(0, line.text.search(/\S/)); - var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; - return Pos(start.line, inWS ? 0 : firstNonWS); - } - return start; - } - - function compareBidiLevel(order, a, b) { - var linedir = order[0].level; - if (a == linedir) return true; - if (b == linedir) return false; - return a < b; - } - var bidiOther; - function getBidiPartAt(order, pos) { - bidiOther = null; - for (var i = 0, found; i < order.length; ++i) { - var cur = order[i]; - if (cur.from < pos && cur.to > pos) return i; - if ((cur.from == pos || cur.to == pos)) { - if (found == null) { - found = i; - } else if (compareBidiLevel(order, cur.level, order[found].level)) { - if (cur.from != cur.to) bidiOther = found; - return i; - } else { - if (cur.from != cur.to) bidiOther = i; - return found; - } - } - } - return found; - } - - function moveInLine(line, pos, dir, byUnit) { - if (!byUnit) return pos + dir; - do pos += dir; - while (pos > 0 && isExtendingChar(line.text.charAt(pos))); - return pos; - } - - // This is needed in order to move 'visually' through bi-directional - // text -- i.e., pressing left should make the cursor go left, even - // when in RTL text. The tricky part is the 'jumps', where RTL and - // LTR text touch each other. This often requires the cursor offset - // to move more than one unit, in order to visually move one unit. - function moveVisually(line, start, dir, byUnit) { - var bidi = getOrder(line); - if (!bidi) return moveLogically(line, start, dir, byUnit); - var pos = getBidiPartAt(bidi, start), part = bidi[pos]; - var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); - - for (;;) { - if (target > part.from && target < part.to) return target; - if (target == part.from || target == part.to) { - if (getBidiPartAt(bidi, target) == pos) return target; - part = bidi[pos += dir]; - return (dir > 0) == part.level % 2 ? part.to : part.from; - } else { - part = bidi[pos += dir]; - if (!part) return null; - if ((dir > 0) == part.level % 2) - target = moveInLine(line, part.to, -1, byUnit); - else - target = moveInLine(line, part.from, 1, byUnit); - } - } - } - - function moveLogically(line, start, dir, byUnit) { - var target = start + dir; - if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; - return target < 0 || target > line.text.length ? null : target; - } - - // Bidirectional ordering algorithm - // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm - // that this (partially) implements. - - // One-char codes used for character types: - // L (L): Left-to-Right - // R (R): Right-to-Left - // r (AL): Right-to-Left Arabic - // 1 (EN): European Number - // + (ES): European Number Separator - // % (ET): European Number Terminator - // n (AN): Arabic Number - // , (CS): Common Number Separator - // m (NSM): Non-Spacing Mark - // b (BN): Boundary Neutral - // s (B): Paragraph Separator - // t (S): Segment Separator - // w (WS): Whitespace - // N (ON): Other Neutrals - - // Returns null if characters are ordered as they appear - // (left-to-right), or an array of sections ({from, to, level} - // objects) in the order in which they occur visually. - var bidiOrdering = (function() { - // Character types for codepoints 0 to 0xff - var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; - // Character types for codepoints 0x600 to 0x6ff - var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm"; - function charType(code) { - if (code <= 0xf7) return lowTypes.charAt(code); - else if (0x590 <= code && code <= 0x5f4) return "R"; - else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600); - else if (0x6ee <= code && code <= 0x8ac) return "r"; - else if (0x2000 <= code && code <= 0x200b) return "w"; - else if (code == 0x200c) return "b"; - else return "L"; - } - - var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; - var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; - // Browsers seem to always treat the boundaries of block elements as being L. - var outerType = "L"; - - function BidiSpan(level, from, to) { - this.level = level; - this.from = from; this.to = to; - } - - return function(str) { - if (!bidiRE.test(str)) return false; - var len = str.length, types = []; - for (var i = 0, type; i < len; ++i) - types.push(type = charType(str.charCodeAt(i))); - - // W1. Examine each non-spacing mark (NSM) in the level run, and - // change the type of the NSM to the type of the previous - // character. If the NSM is at the start of the level run, it will - // get the type of sor. - for (var i = 0, prev = outerType; i < len; ++i) { - var type = types[i]; - if (type == "m") types[i] = prev; - else prev = type; - } - - // W2. Search backwards from each instance of a European number - // until the first strong type (R, L, AL, or sor) is found. If an - // AL is found, change the type of the European number to Arabic - // number. - // W3. Change all ALs to R. - for (var i = 0, cur = outerType; i < len; ++i) { - var type = types[i]; - if (type == "1" && cur == "r") types[i] = "n"; - else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } - } - - // W4. A single European separator between two European numbers - // changes to a European number. A single common separator between - // two numbers of the same type changes to that type. - for (var i = 1, prev = types[0]; i < len - 1; ++i) { - var type = types[i]; - if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; - else if (type == "," && prev == types[i+1] && - (prev == "1" || prev == "n")) types[i] = prev; - prev = type; - } - - // W5. A sequence of European terminators adjacent to European - // numbers changes to all European numbers. - // W6. Otherwise, separators and terminators change to Other - // Neutral. - for (var i = 0; i < len; ++i) { - var type = types[i]; - if (type == ",") types[i] = "N"; - else if (type == "%") { - for (var end = i + 1; end < len && types[end] == "%"; ++end) {} - var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; - for (var j = i; j < end; ++j) types[j] = replace; - i = end - 1; - } - } - - // W7. Search backwards from each instance of a European number - // until the first strong type (R, L, or sor) is found. If an L is - // found, then change the type of the European number to L. - for (var i = 0, cur = outerType; i < len; ++i) { - var type = types[i]; - if (cur == "L" && type == "1") types[i] = "L"; - else if (isStrong.test(type)) cur = type; - } - - // N1. A sequence of neutrals takes the direction of the - // surrounding strong text if the text on both sides has the same - // direction. European and Arabic numbers act as if they were R in - // terms of their influence on neutrals. Start-of-level-run (sor) - // and end-of-level-run (eor) are used at level run boundaries. - // N2. Any remaining neutrals take the embedding direction. - for (var i = 0; i < len; ++i) { - if (isNeutral.test(types[i])) { - for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} - var before = (i ? types[i-1] : outerType) == "L"; - var after = (end < len ? types[end] : outerType) == "L"; - var replace = before || after ? "L" : "R"; - for (var j = i; j < end; ++j) types[j] = replace; - i = end - 1; - } - } - - // Here we depart from the documented algorithm, in order to avoid - // building up an actual levels array. Since there are only three - // levels (0, 1, 2) in an implementation that doesn't take - // explicit embedding into account, we can build up the order on - // the fly, without following the level-based algorithm. - var order = [], m; - for (var i = 0; i < len;) { - if (countsAsLeft.test(types[i])) { - var start = i; - for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} - order.push(new BidiSpan(0, start, i)); - } else { - var pos = i, at = order.length; - for (++i; i < len && types[i] != "L"; ++i) {} - for (var j = pos; j < i;) { - if (countsAsNum.test(types[j])) { - if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)); - var nstart = j; - for (++j; j < i && countsAsNum.test(types[j]); ++j) {} - order.splice(at, 0, new BidiSpan(2, nstart, j)); - pos = j; - } else ++j; - } - if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)); - } - } - if (order[0].level == 1 && (m = str.match(/^\s+/))) { - order[0].from = m[0].length; - order.unshift(new BidiSpan(0, 0, m[0].length)); - } - if (lst(order).level == 1 && (m = str.match(/\s+$/))) { - lst(order).to -= m[0].length; - order.push(new BidiSpan(0, len - m[0].length, len)); - } - if (order[0].level == 2) - order.unshift(new BidiSpan(1, order[0].to, order[0].to)); - if (order[0].level != lst(order).level) - order.push(new BidiSpan(order[0].level, len, len)); - - return order; - }; - })(); - - // THE END - - CodeMirror.version = "5.11.1"; - - return CodeMirror; -});/** - * Created by user on 2/1/16. - */ diff --git a/python/educational-core/resources/code-mirror/colorize.js b/python/educational-core/resources/code-mirror/colorize.js deleted file mode 100644 index eb7060d0a295..000000000000 --- a/python/educational-core/resources/code-mirror/colorize.js +++ /dev/null @@ -1,40 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror"), require("./runmode")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror", "./runmode"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - var isBlock = /^(p|li|div|h\\d|pre|blockquote|td)$/; - - function textContent(node, out) { - if (node.nodeType == 3) return out.push(node.nodeValue); - for (var ch = node.firstChild; ch; ch = ch.nextSibling) { - textContent(ch, out); - if (isBlock.test(node.nodeType)) out.push("\n"); - } - } - - CodeMirror.colorize = function(collection, defaultMode) { - if (!collection) collection = document.body.getElementsByTagName("pre"); - - for (var i = 0; i < collection.length; ++i) { - var node = collection[i]; - var mode = node.getAttribute("data-lang") || defaultMode; - if (!mode) continue; - - var text = []; - textContent(node, text); - node.innerHTML = ""; - CodeMirror.runMode(text.join(""), mode, node); - - node.className += " cm-s-default"; - } - }; -}); diff --git a/python/educational-core/resources/code-mirror/javascript.js b/python/educational-core/resources/code-mirror/javascript.js deleted file mode 100644 index fa5721d5d092..000000000000 --- a/python/educational-core/resources/code-mirror/javascript.js +++ /dev/null @@ -1,742 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -// TODO actually recognize syntax of TypeScript constructs - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { -"use strict"; - -function expressionAllowed(stream, state, backUp) { - return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(state.lastType) || - (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) -} - -CodeMirror.defineMode("javascript", function(config, parserConfig) { - var indentUnit = config.indentUnit; - var statementIndent = parserConfig.statementIndent; - var jsonldMode = parserConfig.jsonld; - var jsonMode = parserConfig.json || jsonldMode; - var isTS = parserConfig.typescript; - var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; - - // Tokenizer - - var keywords = function(){ - function kw(type) {return {type: type, style: "keyword"};} - var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); - var operator = kw("operator"), atom = {type: "atom", style: "atom"}; - - var jsKeywords = { - "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, - "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C, - "var": kw("var"), "const": kw("var"), "let": kw("var"), - "function": kw("function"), "catch": kw("catch"), - "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), - "in": operator, "typeof": operator, "instanceof": operator, - "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, - "this": kw("this"), "class": kw("class"), "super": kw("atom"), - "yield": C, "export": kw("export"), "import": kw("import"), "extends": C - }; - - // Extend the 'normal' keywords with the TypeScript language extensions - if (isTS) { - var type = {type: "variable", style: "variable-3"}; - var tsKeywords = { - // object-like things - "interface": kw("class"), - "implements": C, - "namespace": C, - "module": kw("module"), - "enum": kw("module"), - - // scope modifiers - "public": kw("modifier"), - "private": kw("modifier"), - "protected": kw("modifier"), - "abstract": kw("modifier"), - - // operators - "as": operator, - - // types - "string": type, "number": type, "boolean": type, "any": type - }; - - for (var attr in tsKeywords) { - jsKeywords[attr] = tsKeywords[attr]; - } - } - - return jsKeywords; - }(); - - var isOperatorChar = /[+\-*&%=<>!?|~^]/; - var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; - - function readRegexp(stream) { - var escaped = false, next, inSet = false; - while ((next = stream.next()) != null) { - if (!escaped) { - if (next == "/" && !inSet) return; - if (next == "[") inSet = true; - else if (inSet && next == "]") inSet = false; - } - escaped = !escaped && next == "\\"; - } - } - - // Used as scratch variables to communicate multiple values without - // consing up tons of objects. - var type, content; - function ret(tp, style, cont) { - type = tp; content = cont; - return style; - } - function tokenBase(stream, state) { - var ch = stream.next(); - if (ch == '"' || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { - return ret("number", "number"); - } else if (ch == "." && stream.match("..")) { - return ret("spread", "meta"); - } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - return ret(ch); - } else if (ch == "=" && stream.eat(">")) { - return ret("=>", "operator"); - } else if (ch == "0" && stream.eat(/x/i)) { - stream.eatWhile(/[\da-f]/i); - return ret("number", "number"); - } else if (ch == "0" && stream.eat(/o/i)) { - stream.eatWhile(/[0-7]/i); - return ret("number", "number"); - } else if (ch == "0" && stream.eat(/b/i)) { - stream.eatWhile(/[01]/i); - return ret("number", "number"); - } else if (/\d/.test(ch)) { - stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); - return ret("number", "number"); - } else if (ch == "/") { - if (stream.eat("*")) { - state.tokenize = tokenComment; - return tokenComment(stream, state); - } else if (stream.eat("/")) { - stream.skipToEnd(); - return ret("comment", "comment"); - } else if (expressionAllowed(stream, state, 1)) { - readRegexp(stream); - stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); - return ret("regexp", "string-2"); - } else { - stream.eatWhile(isOperatorChar); - return ret("operator", "operator", stream.current()); - } - } else if (ch == "`") { - state.tokenize = tokenQuasi; - return tokenQuasi(stream, state); - } else if (ch == "#") { - stream.skipToEnd(); - return ret("error", "error"); - } else if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return ret("operator", "operator", stream.current()); - } else if (wordRE.test(ch)) { - stream.eatWhile(wordRE); - var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; - return (known && state.lastType != ".") ? ret(known.type, known.style, word) : - ret("variable", "variable", word); - } - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next; - if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ - state.tokenize = tokenBase; - return ret("jsonld-keyword", "meta"); - } - while ((next = stream.next()) != null) { - if (next == quote && !escaped) break; - escaped = !escaped && next == "\\"; - } - if (!escaped) state.tokenize = tokenBase; - return ret("string", "string"); - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return ret("comment", "comment"); - } - - function tokenQuasi(stream, state) { - var escaped = false, next; - while ((next = stream.next()) != null) { - if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { - state.tokenize = tokenBase; - break; - } - escaped = !escaped && next == "\\"; - } - return ret("quasi", "string-2", stream.current()); - } - - var brackets = "([{}])"; - // This is a crude lookahead trick to try and notice that we're - // parsing the argument patterns for a fat-arrow function before we - // actually hit the arrow token. It only works if the arrow is on - // the same line as the arguments and there's no strange noise - // (comments) in between. Fallback is to only notice when we hit the - // arrow, and not declare the arguments as locals for the arrow - // body. - function findFatArrow(stream, state) { - if (state.fatArrowAt) state.fatArrowAt = null; - var arrow = stream.string.indexOf("=>", stream.start); - if (arrow < 0) return; - - var depth = 0, sawSomething = false; - for (var pos = arrow - 1; pos >= 0; --pos) { - var ch = stream.string.charAt(pos); - var bracket = brackets.indexOf(ch); - if (bracket >= 0 && bracket < 3) { - if (!depth) { ++pos; break; } - if (--depth == 0) break; - } else if (bracket >= 3 && bracket < 6) { - ++depth; - } else if (wordRE.test(ch)) { - sawSomething = true; - } else if (/["'\/]/.test(ch)) { - return; - } else if (sawSomething && !depth) { - ++pos; - break; - } - } - if (sawSomething && !depth) state.fatArrowAt = pos; - } - - // Parser - - var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true}; - - function JSLexical(indented, column, type, align, prev, info) { - this.indented = indented; - this.column = column; - this.type = type; - this.prev = prev; - this.info = info; - if (align != null) this.align = align; - } - - function inScope(state, varname) { - for (var v = state.localVars; v; v = v.next) - if (v.name == varname) return true; - for (var cx = state.context; cx; cx = cx.prev) { - for (var v = cx.vars; v; v = v.next) - if (v.name == varname) return true; - } - } - - function parseJS(state, style, type, content, stream) { - var cc = state.cc; - // Communicate our context to the combinators. - // (Less wasteful than consing up a hundred closures on every call.) - cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; - - if (!state.lexical.hasOwnProperty("align")) - state.lexical.align = true; - - while(true) { - var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; - if (combinator(type, content)) { - while(cc.length && cc[cc.length - 1].lex) - cc.pop()(); - if (cx.marked) return cx.marked; - if (type == "variable" && inScope(state, content)) return "variable-2"; - return style; - } - } - } - - // Combinator utils - - var cx = {state: null, column: null, marked: null, cc: null}; - function pass() { - for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); - } - function cont() { - pass.apply(null, arguments); - return true; - } - function register(varname) { - function inList(list) { - for (var v = list; v; v = v.next) - if (v.name == varname) return true; - return false; - } - var state = cx.state; - cx.marked = "def"; - if (state.context) { - if (inList(state.localVars)) return; - state.localVars = {name: varname, next: state.localVars}; - } else { - if (inList(state.globalVars)) return; - if (parserConfig.globalVars) - state.globalVars = {name: varname, next: state.globalVars}; - } - } - - // Combinators - - var defaultVars = {name: "this", next: {name: "arguments"}}; - function pushcontext() { - cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; - cx.state.localVars = defaultVars; - } - function popcontext() { - cx.state.localVars = cx.state.context.vars; - cx.state.context = cx.state.context.prev; - } - function pushlex(type, info) { - var result = function() { - var state = cx.state, indent = state.indented; - if (state.lexical.type == "stat") indent = state.lexical.indented; - else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) - indent = outer.indented; - state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); - }; - result.lex = true; - return result; - } - function poplex() { - var state = cx.state; - if (state.lexical.prev) { - if (state.lexical.type == ")") - state.indented = state.lexical.indented; - state.lexical = state.lexical.prev; - } - } - poplex.lex = true; - - function expect(wanted) { - function exp(type) { - if (type == wanted) return cont(); - else if (wanted == ";") return pass(); - else return cont(exp); - }; - return exp; - } - - function statement(type, value) { - if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); - if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); - if (type == "keyword b") return cont(pushlex("form"), statement, poplex); - if (type == "{") return cont(pushlex("}"), block, poplex); - if (type == ";") return cont(); - if (type == "if") { - if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) - cx.state.cc.pop()(); - return cont(pushlex("form"), expression, statement, poplex, maybeelse); - } - if (type == "function") return cont(functiondef); - if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); - if (type == "variable") return cont(pushlex("stat"), maybelabel); - if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), - block, poplex, poplex); - if (type == "case") return cont(expression, expect(":")); - if (type == "default") return cont(expect(":")); - if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), - statement, poplex, popcontext); - if (type == "class") return cont(pushlex("form"), className, poplex); - if (type == "export") return cont(pushlex("stat"), afterExport, poplex); - if (type == "import") return cont(pushlex("stat"), afterImport, poplex); - if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex) - return pass(pushlex("stat"), expression, expect(";"), poplex); - } - function expression(type) { - return expressionInner(type, false); - } - function expressionNoComma(type) { - return expressionInner(type, true); - } - function expressionInner(type, noComma) { - if (cx.state.fatArrowAt == cx.stream.start) { - var body = noComma ? arrowBodyNoComma : arrowBody; - if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); - else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); - } - - var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; - if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); - if (type == "function") return cont(functiondef, maybeop); - if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression); - if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop); - if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); - if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); - if (type == "{") return contCommasep(objprop, "}", null, maybeop); - if (type == "quasi") return pass(quasi, maybeop); - if (type == "new") return cont(maybeTarget(noComma)); - return cont(); - } - function maybeexpression(type) { - if (type.match(/[;\}\)\],]/)) return pass(); - return pass(expression); - } - function maybeexpressionNoComma(type) { - if (type.match(/[;\}\)\],]/)) return pass(); - return pass(expressionNoComma); - } - - function maybeoperatorComma(type, value) { - if (type == ",") return cont(expression); - return maybeoperatorNoComma(type, value, false); - } - function maybeoperatorNoComma(type, value, noComma) { - var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; - var expr = noComma == false ? expression : expressionNoComma; - if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); - if (type == "operator") { - if (/\+\+|--/.test(value)) return cont(me); - if (value == "?") return cont(expression, expect(":"), expr); - return cont(expr); - } - if (type == "quasi") { return pass(quasi, me); } - if (type == ";") return; - if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); - if (type == ".") return cont(property, me); - if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); - } - function quasi(type, value) { - if (type != "quasi") return pass(); - if (value.slice(value.length - 2) != "${") return cont(quasi); - return cont(expression, continueQuasi); - } - function continueQuasi(type) { - if (type == "}") { - cx.marked = "string-2"; - cx.state.tokenize = tokenQuasi; - return cont(quasi); - } - } - function arrowBody(type) { - findFatArrow(cx.stream, cx.state); - return pass(type == "{" ? statement : expression); - } - function arrowBodyNoComma(type) { - findFatArrow(cx.stream, cx.state); - return pass(type == "{" ? statement : expressionNoComma); - } - function maybeTarget(noComma) { - return function(type) { - if (type == ".") return cont(noComma ? targetNoComma : target); - else return pass(noComma ? expressionNoComma : expression); - }; - } - function target(_, value) { - if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); } - } - function targetNoComma(_, value) { - if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); } - } - function maybelabel(type) { - if (type == ":") return cont(poplex, statement); - return pass(maybeoperatorComma, expect(";"), poplex); - } - function property(type) { - if (type == "variable") {cx.marked = "property"; return cont();} - } - function objprop(type, value) { - if (type == "variable" || cx.style == "keyword") { - cx.marked = "property"; - if (value == "get" || value == "set") return cont(getterSetter); - return cont(afterprop); - } else if (type == "number" || type == "string") { - cx.marked = jsonldMode ? "property" : (cx.style + " property"); - return cont(afterprop); - } else if (type == "jsonld-keyword") { - return cont(afterprop); - } else if (type == "modifier") { - return cont(objprop) - } else if (type == "[") { - return cont(expression, expect("]"), afterprop); - } else if (type == "spread") { - return cont(expression); - } - } - function getterSetter(type) { - if (type != "variable") return pass(afterprop); - cx.marked = "property"; - return cont(functiondef); - } - function afterprop(type) { - if (type == ":") return cont(expressionNoComma); - if (type == "(") return pass(functiondef); - } - function commasep(what, end) { - function proceed(type) { - if (type == ",") { - var lex = cx.state.lexical; - if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; - return cont(what, proceed); - } - if (type == end) return cont(); - return cont(expect(end)); - } - return function(type) { - if (type == end) return cont(); - return pass(what, proceed); - }; - } - function contCommasep(what, end, info) { - for (var i = 3; i < arguments.length; i++) - cx.cc.push(arguments[i]); - return cont(pushlex(end, info), commasep(what, end), poplex); - } - function block(type) { - if (type == "}") return cont(); - return pass(statement, block); - } - function maybetype(type) { - if (isTS && type == ":") return cont(typedef); - } - function maybedefault(_, value) { - if (value == "=") return cont(expressionNoComma); - } - function typedef(type) { - if (type == "variable") {cx.marked = "variable-3"; return cont();} - } - function vardef() { - return pass(pattern, maybetype, maybeAssign, vardefCont); - } - function pattern(type, value) { - if (type == "modifier") return cont(pattern) - if (type == "variable") { register(value); return cont(); } - if (type == "spread") return cont(pattern); - if (type == "[") return contCommasep(pattern, "]"); - if (type == "{") return contCommasep(proppattern, "}"); - } - function proppattern(type, value) { - if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { - register(value); - return cont(maybeAssign); - } - if (type == "variable") cx.marked = "property"; - if (type == "spread") return cont(pattern); - if (type == "}") return pass(); - return cont(expect(":"), pattern, maybeAssign); - } - function maybeAssign(_type, value) { - if (value == "=") return cont(expressionNoComma); - } - function vardefCont(type) { - if (type == ",") return cont(vardef); - } - function maybeelse(type, value) { - if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); - } - function forspec(type) { - if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); - } - function forspec1(type) { - if (type == "var") return cont(vardef, expect(";"), forspec2); - if (type == ";") return cont(forspec2); - if (type == "variable") return cont(formaybeinof); - return pass(expression, expect(";"), forspec2); - } - function formaybeinof(_type, value) { - if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } - return cont(maybeoperatorComma, forspec2); - } - function forspec2(type, value) { - if (type == ";") return cont(forspec3); - if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } - return pass(expression, expect(";"), forspec3); - } - function forspec3(type) { - if (type != ")") cont(expression); - } - function functiondef(type, value) { - if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} - if (type == "variable") {register(value); return cont(functiondef);} - if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext); - } - function funarg(type) { - if (type == "spread") return cont(funarg); - return pass(pattern, maybetype, maybedefault); - } - function className(type, value) { - if (type == "variable") {register(value); return cont(classNameAfter);} - } - function classNameAfter(type, value) { - if (value == "extends") return cont(expression, classNameAfter); - if (type == "{") return cont(pushlex("}"), classBody, poplex); - } - function classBody(type, value) { - if (type == "variable" || cx.style == "keyword") { - if (value == "static") { - cx.marked = "keyword"; - return cont(classBody); - } - cx.marked = "property"; - if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody); - return cont(functiondef, classBody); - } - if (value == "*") { - cx.marked = "keyword"; - return cont(classBody); - } - if (type == ";") return cont(classBody); - if (type == "}") return cont(); - } - function classGetterSetter(type) { - if (type != "variable") return pass(); - cx.marked = "property"; - return cont(); - } - function afterExport(_type, value) { - if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } - if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } - return pass(statement); - } - function afterImport(type) { - if (type == "string") return cont(); - return pass(importSpec, maybeFrom); - } - function importSpec(type, value) { - if (type == "{") return contCommasep(importSpec, "}"); - if (type == "variable") register(value); - if (value == "*") cx.marked = "keyword"; - return cont(maybeAs); - } - function maybeAs(_type, value) { - if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } - } - function maybeFrom(_type, value) { - if (value == "from") { cx.marked = "keyword"; return cont(expression); } - } - function arrayLiteral(type) { - if (type == "]") return cont(); - return pass(expressionNoComma, maybeArrayComprehension); - } - function maybeArrayComprehension(type) { - if (type == "for") return pass(comprehension, expect("]")); - if (type == ",") return cont(commasep(maybeexpressionNoComma, "]")); - return pass(commasep(expressionNoComma, "]")); - } - function comprehension(type) { - if (type == "for") return cont(forspec, comprehension); - if (type == "if") return cont(expression, comprehension); - } - - function isContinuedStatement(state, textAfter) { - return state.lastType == "operator" || state.lastType == "," || - isOperatorChar.test(textAfter.charAt(0)) || - /[,.]/.test(textAfter.charAt(0)); - } - - // Interface - - return { - startState: function(basecolumn) { - var state = { - tokenize: tokenBase, - lastType: "sof", - cc: [], - lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), - localVars: parserConfig.localVars, - context: parserConfig.localVars && {vars: parserConfig.localVars}, - indented: basecolumn || 0 - }; - if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") - state.globalVars = parserConfig.globalVars; - return state; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (!state.lexical.hasOwnProperty("align")) - state.lexical.align = false; - state.indented = stream.indentation(); - findFatArrow(stream, state); - } - if (state.tokenize != tokenComment && stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - if (type == "comment") return style; - state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; - return parseJS(state, style, type, content, stream); - }, - - indent: function(state, textAfter) { - if (state.tokenize == tokenComment) return CodeMirror.Pass; - if (state.tokenize != tokenBase) return 0; - var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; - // Kludge to prevent 'maybelse' from blocking lexical scope pops - if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { - var c = state.cc[i]; - if (c == poplex) lexical = lexical.prev; - else if (c != maybeelse) break; - } - if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; - if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") - lexical = lexical.prev; - var type = lexical.type, closing = firstChar == type; - - if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); - else if (type == "form" && firstChar == "{") return lexical.indented; - else if (type == "form") return lexical.indented + indentUnit; - else if (type == "stat") - return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); - else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) - return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); - else if (lexical.align) return lexical.column + (closing ? 0 : 1); - else return lexical.indented + (closing ? 0 : indentUnit); - }, - - electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, - blockCommentStart: jsonMode ? null : "/*", - blockCommentEnd: jsonMode ? null : "*/", - lineComment: jsonMode ? null : "//", - fold: "brace", - closeBrackets: "()[]{}''\"\"``", - - helperType: jsonMode ? "json" : "javascript", - jsonldMode: jsonldMode, - jsonMode: jsonMode, - - expressionAllowed: expressionAllowed, - skipExpression: function(state) { - var top = state.cc[state.cc.length - 1] - if (top == expression || top == expressionNoComma) state.cc.pop() - } - }; -}); - -CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); - -CodeMirror.defineMIME("text/javascript", "javascript"); -CodeMirror.defineMIME("text/ecmascript", "javascript"); -CodeMirror.defineMIME("application/javascript", "javascript"); -CodeMirror.defineMIME("application/x-javascript", "javascript"); -CodeMirror.defineMIME("application/ecmascript", "javascript"); -CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); -CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); -CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true}); -CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); -CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); - -}); diff --git a/python/educational-core/resources/code-mirror/runmode.js b/python/educational-core/resources/code-mirror/runmode.js deleted file mode 100644 index a51c6d0d5229..000000000000 --- a/python/educational-core/resources/code-mirror/runmode.js +++ /dev/null @@ -1,72 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { -"use strict"; - -CodeMirror.runMode = function(string, modespec, callback, options) { - var mode = CodeMirror.getMode(CodeMirror.defaults, modespec); - var ie = /MSIE \d/.test(navigator.userAgent); - var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); - - if (callback.appendChild) { - var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize; - var node = callback, col = 0; - node.innerHTML = ""; - callback = function(text, style) { - if (text == "\n") { - // Emitting LF or CRLF on IE8 or earlier results in an incorrect display. - // Emitting a carriage return makes everything ok. - node.appendChild(document.createTextNode(ie_lt9 ? '\r' : text)); - col = 0; - return; - } - var content = ""; - // replace tabs - for (var pos = 0;;) { - var idx = text.indexOf("\t", pos); - if (idx == -1) { - content += text.slice(pos); - col += text.length - pos; - break; - } else { - col += idx - pos; - content += text.slice(pos, idx); - var size = tabSize - col % tabSize; - col += size; - for (var i = 0; i < size; ++i) content += " "; - pos = idx + 1; - } - } - - if (style) { - var sp = node.appendChild(document.createElement("span")); - sp.className = "cm-" + style.replace(/ +/g, " cm-"); - sp.appendChild(document.createTextNode(content)); - } else { - node.appendChild(document.createTextNode(content)); - } - }; - } - - var lines = CodeMirror.splitLines(string), state = (options && options.state) || CodeMirror.startState(mode); - for (var i = 0, e = lines.length; i < e; ++i) { - if (i) callback("\n"); - var stream = new CodeMirror.StringStream(lines[i]); - if (!stream.string && mode.blankLine) mode.blankLine(state); - while (!stream.eol()) { - var style = mode.token(stream, state); - callback(stream.current(), style, i, stream.start, state); - stream.start = stream.pos; - } - } -}; - -}); diff --git a/python/educational-core/resources/code-mirror/template.html b/python/educational-core/resources/code-mirror/template.html deleted file mode 100644 index 671b80471fa4..000000000000 --- a/python/educational-core/resources/code-mirror/template.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - -

-${code} -
- - - \ No newline at end of file diff --git a/python/educational-core/resources/fileTemplates/internal/EduTestRunner.java.ft b/python/educational-core/resources/fileTemplates/internal/EduTestRunner.java.ft deleted file mode 100644 index 3d99c65140bd..000000000000 --- a/python/educational-core/resources/fileTemplates/internal/EduTestRunner.java.ft +++ /dev/null @@ -1,18 +0,0 @@ -import org.junit.runner.Description; -import org.junit.runner.JUnitCore; -import org.junit.runner.notification.Failure; -import org.junit.runner.notification.RunListener; - -public class EduTestRunner { - public static void main(String[] args) throws ClassNotFoundException { - Class testClass = EduTestRunner.class.getClassLoader().loadClass(args[0]); - JUnitCore runner = new JUnitCore(); - runner.addListener(new RunListener() { - @Override - public void testFailure(Failure failure) throws Exception { - System.out.println("#educational_plugin FAILED + " + failure.getMessage()); - } - }); - runner.run(testClass); - } -} \ No newline at end of file diff --git a/python/educational-core/resources/fileTemplates/internal/task.html.ft b/python/educational-core/resources/fileTemplates/internal/task.html.ft deleted file mode 100644 index f6833496d1c1..000000000000 --- a/python/educational-core/resources/fileTemplates/internal/task.html.ft +++ /dev/null @@ -1,4 +0,0 @@ - -Write your task text here. -
- \ No newline at end of file diff --git a/python/educational-core/resources/fileTemplates/internal/task.md.ft b/python/educational-core/resources/fileTemplates/internal/task.md.ft deleted file mode 100644 index d822b90838a7..000000000000 --- a/python/educational-core/resources/fileTemplates/internal/task.md.ft +++ /dev/null @@ -1,3 +0,0 @@ -This is the markdown document. - -Write your task text here \ No newline at end of file diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/CheckTask.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/CheckTask.png deleted file mode 100644 index c9305d1f5141d50f322000499cbad7828a2477e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 411 zcmV;M0c8G(P)JMuE&3@F3_3c za6)UMEf+2q=uLO7w^$nR-)edAf2=MrnCto9YMTGWj;@YbXmX3Sra0CcFY^6wvmxrg z)tWHu1{g2(|8KO|_rKl5fP+B(2AF1-2_c*H=X?FPSR4M|W^>Gcvz5UYu^M2sJmA0S znvnmx^F981^+hZLiuF771#Q-u>;B(lb?|?SjS>IN)`wlxTj(W<)c}hHLAQ<9hWs~J z?)P6~mh)e;iC$+kXE}rU#%qKB8?6q!sI$OB6ge7Eu=;FAe!Uew=L}Z+{nuUQ^qn%W(~L(u=xN0002ovPDHLk FV1nN^%JTpK diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/CheckTask@2x.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/CheckTask@2x.png deleted file mode 100644 index 781218a56aad49f0e4f6a31cdbed63aa6edd3669..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1064 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 diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseCreationProjectType@2x_dark.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseCreationProjectType@2x_dark.png deleted file mode 100644 index 65286037dc76b5da9daf254388437101b82ae961..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1489 zcmV;?1upuDP)5|JeUOIsK#__z5@7>3K)Z1!sa=xAX}pp<$R0Kza_UPmCG&li|^e`kCMz+NH> zHnHwY08Rkd4*>am{_XBen6C7++kz+{qM%qT7A~DEj$<2!VdFZ*7hFvP>rgBf3w!tO zecbcBzdv%$MKd!q$EK&Je@T+WZMFz6gXej{8Ubcb78VvJYPH(Q%?y6cZAe4}Kx?h_ zri=4x51Bb(=7fk$sZ`oGHa7N9UteF}R`|eWaLz^Z^YarT!lhE_F3h1vf8{^1}92Gi+u zp})WX4&V3FXV0FUs$PiBol3SpXjN)AKxvTb4iv&BU+kutU$fecU2_R8ReQZ&U#!>b zi^iCrecw-MtrY+jMbX0VRN9dWijVbb{!EiJ;P*TUMVWRf8LehCG#@p-ya|7X*G%9_ zupM-B)w!N>CYQ}6&Bl$flU6pTjW+Fu(IOzNGa|VH;y-0mc0*=fX*pVGSiKk~YDI~g z)8f7rkT8_bY)>cFCDt(<3qT-fouvJ^GBz@utyFgSSIj`iyRT-e7c0#|?;o6fKtz?H zwDCkJ!%ZIw0E44wm5dKks=YoILEyLev3XnBvlG7?jS{j!b(6GT>NeK zOQy^1MnvS$8tW^e;M?Q5OlF_SW;+IX1s`Dn*sYb!<SUe@tUcyFB#gnd7o>$us zr&6hrE?xXSkgp#d+cv#aNVV3zpYs~Dv#-`al&{}!QYexlfC%Ea#+vujMmylX1Mdtg z?R{-LGt!A3?dq!@dHJ4Cwq?}nJw(vml@TEgLLNkutcU8Ph`=H&>W`*+_{|ObKq~^$ zS+;DsBQQSyaOtb2kJeLwG5|r=*R1Uy7@WW8zgmXMZWneApj)A`g$)x}U0q&DXNo7- z@hAwyiEZVvtT<*vo(2eD76|jY6OmI+NiI_YJE+y4zZKnk8s?U%4HKAr_rSt^2i|*w zndbo9%uXs9M4n3+qy>P$wX9{xX+>D>*s&7N{3@buCth8m*5s#Ja^Jb_yXMUGIN>*M zJ^%giGw)9X0n&+$Y@nA8IGcuY?c}o)an?C2Vj)5TtddU6g1iXE8*+uGU$ms3hjZVU z>)prCeDGZB4A7SRy*CIaENqxa6G-cn#O^k0`3G&3gscV9pq0UO#f=$ZLD{?D>E;gTTqdLxZiz zz9KA-h)`Oj-3kPbNKq4-j_Q#Y4s-CtM*u7ly$4`LL{>J1008{>VSmm>@ix-3ZccF| r0|V*#rP0qz*4je=hKVR%bKt)K`H9GeKJpGR00000NkvXXu0mjf(D}Ax diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseCreationProjectType_dark.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseCreationProjectType_dark.png deleted file mode 100644 index 3ba8a57887d7a34281ebd517d39d89742a47496a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 654 zcmV;90&)F`P)S}qg0YFZAMMcGOAl*+a2C82Vq{U$_ zCUoTs>Y;$dGZ3|Gm z0UZN1#{=p8SPcMaK6>=%w~ZS&9&K%H?Iy_pkh7OAUAlSo>ec&z;3?3MlM^RSoDP)F z%POwVODkzCg5ahopx9yz13>-*IU6X^G-b+^nM;-|SwCymtl3$$eHBiH)7Lr_&pvHm zGWUr?(dUG?i&|Hr3!)tA(| zj+xt6`KkZ^&%g*XU^+B(!R6LUFw!S_H17D(zk2lAD{NMUuG>y zJN5dK{^s{rb)7-!ARma&g8~MIfrhU3E}ppArflIq`|73toa>gqcW+$&##>j_$v9@kj~- zA#i1ttl!Obimz{NRQ`W`wfx;{t7P|HJGFS#wQJWF0kJm`2FM6qUM!h%X`xizg?ZwY omzPMyUcGpz`s&rIYk)W(05u2NCwC6tQvd(}07*qoM6N<$fGZ zx^prwfgFWok04(LhAK4%hK3dfhF?ITh8GMBr3MTPuM!v-tY$DUh!@P+6=(yL*b?9q z;`;yp|EW`_hJ=JzE)6hQ;%~Pm%xX=T_1XyIWdS;K-8E)7sZO_3o?@jq(M*1#iT&CL z`}I+ltHaDz1{{xmqm`0-9zz*8`~1Ze4`cnsBpK zAx6vn^%i?;FLc+K?*vwC?X)4aMZKNqwQc&B_pm z4N)LxtOx|zve-j&fwS6NdzCphKnGVewj2YxU$P{~FBnKyWL0FrKm{CJ_@e|0KE@<( zcNc~ZR#^`qhqJ&VvKUB%*d7dSC$sHY^u1QU%z0*k~ND~E#rIik(+~yv%90K z)0^S#w5u1cT)KAg>Sc2SmQPE1dVEEE&z{lMSt}vIASA+j{?)FK#IZ0z|d6Jz(Uu^G{nfj%Fw{d*i_rV*vi15;#$fa6b-rgDVb@NxHWKg-%$W+ OVDNPHb6Mw<&;$VT^})~p diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseProgress@2x.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseProgress@2x.png deleted file mode 100644 index 5dad0c05270c3af0cd09b0276c8cffd16e9ec538..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1052 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI13?%1G+4F~iftew|C&cyt|Nm2`P7MhOaab2= zu_VA^RfypNZ}%NZwyVSJH^-VT4>VgIq&L??YlgGNG)MKR_NtR@l_%LKO|(*+V4=`& zCf{c&*JCW#V<_8WAlt3yur=OtO_=er0K+AI28(_47JBI{@X(s?ra8|=eU78rY}&2mtk z3DjkyJk3gJnuXFdbET=K9y?PUH%0>;X|*cUd_^$OoyJT3fgUlM9%Q>N!ggbf&4wt;brI%k!%WwN7_SO4SmCd?+)rRM%Z8PCJquwkOzb zjkDeoW4SrXd}D;k`cR{F!G>!C^jG`pt@74c>8ZWkO>3Ep<}zo^r4G)UVu0Rs*brsA zKGJ$^1TbvPR)v_X3^HC3Xt>N@Z;6l25-;t=9$E`sH5WLm&v#UtYp*)TR%MQj%4|zu z%mU+j6yOg5J4<#ypbD3gAirQBJv9*m6Hx&$WuX96SuhVO|JUiw2B10Zo-U3d6}OU8 z8W`Nt*-8!^X%L>EezfV#P40Bd#ommu%nScD^Olq*rxkurNNqi}vsv+oLztVGn?Txv ziI2;h6qh|-=y&*;e)_K##mAg(DIb^=RZnzq%~M|Db79H4!%JEuE;63cTe^A2k~0c> zq9c~{9Gr7oE#gfQb8(3Q&ynX(@AoM6^hC^MWO#2Uc;n3|1sE>zGg$1Sx6n&xfrr+7H_dr2>T?{`X4|XI zvQwF1tvuaQ35ced*{un;SQ=oyEYNIOpvh8yqs4v(i$J<`=X+|+bJLvbsxik&ZI*-T zOrS0sxG@^&NUK$$<|~4M?lfNN5A=x9ELXc#p=OJGf#N_< zna=fa*&J)VGQ?qXtmF1XyDhO+8=@@MN0_Y(Gg%X2xGKgMlX>L1`UA84SZH)&8pzWp@@4e}sd(u31r@HP+aoUmOusy+c zYn=6#7|YF3<{KkS)`uFc3pQLEpugHzZAPMT-+~iKTT8Jl{QGCqlmhyo~QT0Rz*F5DVJ{Oj(JG`Vt;v(Z2y``IXEIFgF zCpuzD&%rsj)gs;`F&CE@@Em#m^nQ<0Pfx^LMuzuxf@d!1yGa6_#Ng@b=d#Wzp$Pz# C-Yqr& diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseProgress_dark.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/CourseProgress_dark.png deleted file mode 100644 index d7c9b3b7a0c6404afe090100f1b8179b7cca57a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 597 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4uGBwbBK{D2fox}&cn1H;CC?mvmFKt5w}kh>GZ zx^prwfgFWok04(LhAK4%hK3dfhF?ITh8GMBr3MTPuM!v-tY$DUh!@P+6=(yL*b?9q z;`;yp|EW`_Uc7kGa%q6c5`VijVODFxtk*^uFALC_>#i}wNp-rN@)Rq@iDvQ>P3+f3 z*sqVWTpeb%GT3NEfZh@x?M3dI^PSb^*s0960h(hp%hhUm5YRN!xgJ23cIzUn)`Xj_ z3Nc#lueaD+d!f6=d?&DCYo`s-uGIh+L^k;On7#P(otJDF_L~6o48S NJYD@<);T3K0RT%po!<;M4ufJ4bUh} zT)e)`%5=%fC<`DY4gly!< z{vbpBD?(R8sZ?UVnFK`hlJaqZp!o;M$U+_!APus`pq{qCx8p@iK+sbVM|teNCJD9l zUI5?K=s_%E65#vi5R44?o<^tzBMoou$bP`zjf2;dFiohp^#nF5p`XBbJZ9#Z32b&^ z*y?NWc1{Sjzkdc-S)C7HirIjt6@~XD2GfL^!rm+P7r2`dc%F~pYJ>^3rxS(aPO`s% ztE+IgBj9Q(p?0^zSiRk|zkq#|!9A&9t1_YHnkrUqcI+>3Rwb-A6*wy*p=KKjmahZ$ y7dRdSuyqlRvOuWUY9f}deD>eLJX23~Vc-k#9%?1=5uLFB0000#t8hpk@vPMFp1|FPthU zP)d3R;81`}^kJXV2XOlK{815zt5Q9XQ3*7WYdB{Cv>9Q6H@pWB@1x7$Oi(07Il(_?Y3CXsfUs-A#S5?hND9QUDbdD`6a_iQVPc zKVQ|61+*>&7>zc6I$}=zj5JmEC7?h+*4XU_Yu@I=7nZ>x;sE1O>%)`DrE}J1S=QLU z9$2nIOxv4<)+{evoc$iFq{gWH$1d&_bXd0rUit%R3-zUm41k!P-!ZM^-wI5aneCr)=T5{oC)l(x)MwL>;qf7g{`-1qo2J0|BSL95^MA`Q3!7F z)cIENnIO~5ZIt3deX+Wdld#%1%8a*5ZSRNk_u18o<0&Hpx|8;(;$XPiNjv5TxbJK1o%8(iE$foZ$Nva00OjHw^|`@PqW}N^07*qo IM6N<$g3hfyv;Y7A diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/EducationalProjectType@2x.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/EducationalProjectType@2x.png deleted file mode 100644 index 6340e89b1f3fe38df1bf2ec885a7e076f1983b7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1340 zcmV-C1;hG@P)4UH0gGha`MG&<}3nkQ_()hLymQ_kbF^Kvi5`6SUB2tSQ zP$MM4QjijxNG92x-Mv44cXlS3ot;g06QQ@9%9CUPj&-<#vyp@L}vh$(&sp5Uvi5t<0-JUy_2q#ibx@` z)Um13@yV_|po#8uIpH=xm(RP)Za%l)wk;of(|C1fTG!Yj8iG_S0k2f5Z)2+m04f^* zvpDfQcb`}AZov8_N?F~2fHz1x08LcRA?BS@`an+{=#&k(lsK-#D5E=9{K_=+Dt*Pm zewXD!@3eW0QDOr`&db?70Z^UN5tlH|7VvtfkZRZfl@bDvW!xWy6Q1Lo%uU|Abq^@5 z3_tDx4+ir6UKnU@vv8jhdK4-bGeJ;Idi8*@=T*oF$nH4Rgpj0Kt&x}_=Ar5hLP0iT zr3Eo=Z_5V!U~t$i;^SPDH4-udxv)4FeB1^A>opwsKF5K~92ohRF-977VMt{xZi%(! zAj~;~G=q@PhqG>jXXBJLO7}s=2llvXX+HR*1pt`)iktU99w)RcNwDTenx=-~Mwoh1 zsSFPwbS6r=`P1+`-3&GUy@lOY0FIZt%j@&NV4E8ih$U zb@#S~z;+zQZ7YNu>TIz|;i5?`)e#Lhga{&1>ZZ5sHUM-Dq1tkTft&w)de4vVDxtnL zhER(&yM%csbmvN_7WWE`gS9abV{8(Dv4 zck%BR-#PrzVH~wDvG0Kb97h%t-R%i@;Gh!PGOp*~lujIru!PUE6OXieAhRE6a`mMH z-#q{1?U%onH9gX87$`SsYMo4zHiF z^kn3dM18<=A?H(0`D_iq%*3fSm&FrJby>9yEgb1+Kr{k?j4sE}G7FsovS$cn?avsL z{!Q(zT3u0ChTLbwa3PWJW*)H6z*ZJ~!fZb074sti)n8d$xrS#xb&4t5gPZ!F5oP?E ziFx4HLbsbx#1hiP>&B92FgvES|`(Msw4!?fDGVjJ%4m>^_*;}ag$D1H1iH1>9oZ^bMk-K z%v6tGvGZ=Kry~MVov@IYLKSQV;m;^U&r6{QH-!gY zKp0D%o-`Yw59OBHrHFRaddCvg>EUSmXp-!RhEUn^g_+82UW5Tg0u)3#$z1H?wvGUAL=r+Z-o%FNrY^A`NOTQ5`e z5iMG)mA|}|hz-??&y~h zW&EazdEkc;?Isk7MKUpSLKhLLVa)9|Wf)<+v8|SDv*DDSGwVyOPXj+-u)V)1SL+wg9WYPC8Lfd2&;00Rb^+bP%5xc~qF07*qoM6N<$f(Ax;i~s-t diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/EducationalProjectType_dark.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/EducationalProjectType_dark.png deleted file mode 100644 index 0f521a88a9d4c8be4cb2d951dbd5e55449be751b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 674 zcmV;T0$u%yP)X1$|eYcjbOB}Fd|$hi6)}i z+{~Nt&Foz~a%kh+&EC%2ci;QI@4ZFuJ^as7TLZ^$%+!=t^HPdBBE}dO@kH*mwRGmy z{maX}`JxZ8q%W4MS^Z^W5(l>rKy7#^Y^)Adj;cgW{*%Gdi+v#V)HYetaJKQ(>qLXr zDlh<#0#t5BLQWsR!K)8;2`SDieM@B(FhsuLy#p|IlpVShq&Nlt(!l; z+NB7f68&74^ted65*9|3k|%ilaPCw@kT4sS|8fq<;B+fdfL%7;%ZgG0mt#-sH&_a>ekbFRp8*8TCo zaTQ|P!7Oxke&*!#m$J_5jLKi^;+{i~cb$c2;Xua0%E)X1Kuj-e#~?TN&wWmD{hDVF zHYs6;8Gl;xaWfv0cx{Rfowtq=&4z`&<<=-yVeQ2xrW&e+=hQA}C-iZl`s>jhzC9Ef zn0#YGAYQP>43}1|M>sko>^7G$7imU zsQ5mXqY+Oh^8WDWb2B`UG3|Bz<(+aai4&rDnAe1}{4c*F;VZ(n@~FXqtlS^<$7MVJ zi{H7&uq;vHn7bG|NVvlFLH+8p7ym!M7xTNGPoVjRLY3Z>ULC%m?!<<7Lht_{d+mIn z;@Q_w4U=6K9=dIev$$jay+kkoD+*s^0Fs)rj9G$@?NO@3noiZH znNCleHZFA$NMnAK?(m1BW99~l11wXk*+RP$9eA!}##VfCV-NAwF=Os?RxSM~v4A_Y fSmr2$mjT1~{Gx=1@mCfA{mbC#>gTe~DWM4fF@SV5 diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/Lesson@2x.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/Lesson@2x.png deleted file mode 100644 index 2b0c0b83271b99feb1ecf605512ce7d4d5f7a6b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 751 zcmV41yK;9eRZ0ueHfQY43#Igb~ z8jVWiWP)E#CElO_3Y&B3jK8lmrT$<5_?KF~05KIws!9m3@wN_emD^Pj&lUnCn)g6! z)vF?&Ed)ra%Z0AFKo#+9A%Mn`1FhYlig>mVAig#m$&H8j2JCelr~0Lws*+PIga~3qu z^wcoxRSDHiqaC>Q-b{5tTRFVGd8FA3p=~r!^YY#sNNvc6d9V?At*2@Kj;137Q2V(F zvpz3*dA9gl@F)}U*8K#K*<4JueWab%)dTg|Y|Euayz%+In>f{Tl}^Jp>_CgF8(SI- zwC{l9B*xfj^iKBE3Gf`AEd+?I$v|9PHZ{z!AEo-mNE@#A*HEo}ei)nTb5PuIflgv8 zYnW)$Gl*#nejmZUmJ_s}gKz-TM=ND|T%De`Xm$5c&7ZpBor%5g zaZ=R>3x-(J;aN95Y2oWLaI?>XWvH1l##o*2D8o;7^EJ;F0z}{6hV@T&sv@2(1Xxp* ziWqacD&pBffYtYsvG%b}74d8#z^c0%L_gH3BA&hce~yAGQe5c z5vGIzkRr=C7b?ICCbz`+g()%&fJnbLkmL4e3jy+EidN002ovPDHLkV1goNS+D>A diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/LessonCompl.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/LessonCompl.png deleted file mode 100644 index bacc7ded6755df0119198118a17581a83969d05b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 483 zcmV<90UZ8`P)p=-i(qSxO*}8_>3GM$4uNLm?+Vi=Sk8*I zjnRH6(ncs!Ml^+Wm_JyA?{pOnel6rl!%t5{$Wnsx|L$4w!tM2d@wDawTxDcC61py;wE=mN| Z@C|`0WR&dkY%Kr)002ovPDHLkV1jsFvRgW!FIZZ!rSFBlATPy&&`Nnxr`y$*Y%$r5$-EQYvaY8s!jQH= zOIx74i(*{18-i0a7rkN8IO8(RHqEwdE<=Uyp65`Yr*xjSZga^_@=5cY=lOkp-=6g3 zd0GIV;6+mKp9vQ(MKB28r}e9y=KVIT60F$XMxmT1@Q+u z*v_;eun>XhlonE7736*EYp9zyfI8L$=-4>v+O|eF>VUGp0T#a# z_kaE#Pghsb96SnD$i7}j)=@Wa0PVy9m@gh#XZ{GP<_?nv9i)K^`a~Oy)15G#Kg`b2 zHKzI3Q8{}M+NcxyGcEY~{%ver3wN$U9d1F@xkKz)>gElgN;qJ=aEQe)N5lyi6ro1U zEL}qD$lKTxY=kC3p2)dQ7|$Psex?Jer~`ZaHZ0!0hSaP7atsxbX6R=-SRZxs22hOd zhiP&DSRw7Hh1hLD&*y`j<`-^$ zhEh*8c843GoNz)l-OBo?oBu5sZh&IS$x@zZfi!4C*QecBTU+Ba@m>m`@VE*mXBp1@ zg}tv7#hsf`HLN@3G^XAMdfvAN=|d zdd3ccR|nyM3F2Yv_WC5$%^N__Pef7kc81L;iI`9vG@{U}!EW*vEZn(*y?w1@tY+^E z$$ZAzB-G6tpshIsDq$agF?Rwf*v*bW;zrP8J=B7pzgnAJEmJK-EKXM z2MoioW4aZ;@bkd)2H^0r`c?XQEalks2ECUJbZtuAu?+KB9ZHpHsT*?$U{9-6rp{-3 zDjrM`{6yH9!=ZC$vSy=xi1$>S7T^`aN{ZlY9;fC{QtDTd&a4BxpY{m#c?8g>IZk97 z;3dLxO5r`ijywYFNY-w6mXmJY0Bvc>Pv_BmgRV4Jdo}?U(~2a|5kQjWT6|`J|K-2q ZKLO9y=A8E4o3;P|002ovPDHLkV1f`?7b^e& diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/ResetTaskFile.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/ResetTaskFile.png deleted file mode 100644 index 9b28963b25fa2922a3a9db2e8e8f69e6010fa566..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 474 zcmV<00VV#4P)RSpK&{7&QLWdaOJj*Wh<_i5 zFA(j5_O6ahn~B~>b*4Fh_)$RI1jOw`xj?zglv87p%|^rd?*EMzdH&a$YM18H9XQ3Q z+pkxp+tf?B(}WkF3tCMkxSiLV@Altxb>M%~RRRC?fCg#LbOK||Y4-osCR)E&>Nbl- zHvksOwHf^drDn?lUYM>A{%^W2=)dK%;4|h+15TPP^*?5~%;&b=B9H$X)9wE$_L#ZC z4ETu@%2$Cnro68v(sF6gd4mBK}!&fo3sU`T6DlPnBS-+BHgO%tT@U1hQS{CnWr?(ToR-Q04hu~Wszz` QeEgQ&X; z{-pv;!n?0U^Q~BB{D3XJep&$g%2CS^e2W~PRHxqZXgyzW^X z(C}j?)bV!(t?NEF-vRE`5O~r9B?U|vQjV&Rz;wo=V>Ib5-MP3H0(m;i_bV>p&QaQzSjZjAf}y>@TB0?xI*$64(I$4W2QQ$esUb^%tq8cYgTSMV1uJOOq@VS1 zF`%zP;73HGzx2rZSXR0g{RM$y!kMwHy}+@v$k)vFg3%ng8$9|Q-smZh$pVu6~mEOipSCX)S=_L4Nk1Y*7*57{%l z;M^Jj$7VlRmz76#`L;JCy`(YbB)SP(Ng7V^qrDh|<|w0de}2n9Bq;?5oOMH#qE57@IWgDn&M zp82@-g~|mcIM7X2$9~F`4I1MPFrJryUFibbMi6XQ4gkBVyxDwCs{FrUQ^E-^&A6*V zpLBy^&I6{!09aN!!HT=*t^{eDFfX;@nnL9QrUf6gEVorjI|g?Eo9@EWE5L1Ze`n1o zV>m_(vv}7`ag__0mt-_os$~axh}=d#K#}K}OBg~qowS?lkb{8h_h_9X zZYoX7JVoVMSht?hzuDR4>TyMl(UDc!QJ^P`ON+%m-~I_cprDpL_N|VfOiRSNQdKY|0-%_C!; zUZYZ8l=R?#zlQ6ixk3P_E N44$rjF6*2Ung9*4X~qBm diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/Sandbox@2x.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/Sandbox@2x.png deleted file mode 100644 index 58665fa41a1be347d9f6fc48d0163d02de4ad32c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 455 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@jG3M;jv*Dd-b~-T2{WgK$tp|@jbeDI!B-Hz zhHqP#bi+#hXjz}Nb`0MIBz$x4HwM>p*OfeY`!dGh#We481(m-Wuk)`LYJTV?R=< zzHdXX8J9xwt#u)n_sLJ;?Ebo3&M2(txDs^Z+{<${%mUMDd4D}QR4&h>y(1jM+$o^#R3 e|GfKIBp5noo0;_N$cYBV41=eupUXO@geCx-bHcCy diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/Stepik.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/Stepik.png deleted file mode 100644 index ac989ce5c96db33b294b1d35a4dd756829b00671..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 285 zcmV+&0pk9NP)yb$3bb(G7Tq*}gDI9vSy( zI0#h&vxtd2^t(QLkP+D=&T$!!BU%#?E$}Qfk+)~>B&`5yB+;7yBnoiLl5u9uEVO}9 zK#KXZCc5;Fc&>nMK|lhDD+YNjyv`nHT=n!dnL(R|_;LGW`W&T+TzD6N46GE{BtinY j%KGdpy@$$=>6h;T(^Km$2+9ML00000NkvXXu0mjfdzO7+ diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/Stepik@2x.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/Stepik@2x.png deleted file mode 100644 index 2fbd65c6a4cf9b1cdc7a9e9fd0b8bf7591a506bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 439 zcmV;o0Z9IdP)zkMkeQgULfr4qnaR)sV7 z{%!agtB{}oJL5HQ!oYoB$+Bj$0J@ubk;an>cjwvba9v3NbslR-KDgU8RRY|mV*q%L zAp03RVYb~i!bbQig@A;yU&cr*_g*R-QzfCV3|-T})n#amCgLt;60;6m5+EKtXsg7K z#iYKkfvdjI-+L@y hiyxD<_%-{};}du7I%Sr>?&1Id002ovPDHLkV1ic-$Jzh@ diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/StepikOff.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/StepikOff.png deleted file mode 100644 index 23c541ce86ba678c0cc4eaf3d8e9da498072ea55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 479 zcmV<50U-W~P)g`xO%roG5V&kVv4WPmdK^!pvqGLX2Ja z_Pw{Nb2(?t=kqt-jYi|6*Xu=t!QhLB-~8v+WR}@@=iTjgKl}aueKMKIa5$9FXe8tD zSPaRXJ-0Rk?sPhN0Ol>NRx6&(W{>G~`Ut}?HUwhSu$O-!QAuoYQoG$2BR4snPNHkI zT1mNFPNFEXsvMFZJ4oYX!1a1PIiJrKc)Q(Xx7%4zp-_-wv6z@z`>l3tV0cD0CoA}J zxp1mfDiv9;*RtJiWwY4;PMXc8fU&`nFoEG&Wdp8OtK$27czo`WSHbX!W#i|Ap$RJh17Xr0_EH`-;^09#(Y^fUTrWQ0r zf*~M+BCzFhX+z{uFbW8oz|#=5jYwixXqD%AvREu+zu((dDwq~ykCvxxbO#9%Gj(`Q z4u``_@|+BqVap7XJIajfb68@=n85Ii?B|&0|GW=K6ciK{6%`g178e&67#J8C85tTH8XFrM92^`S9UUGX9v>ecARr(iAt53nA|oRs zBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7EiEoCE-x=HFfcGNF)=bSGBYzXG&D3d zH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}?K0iM{KtMo2K|w-7LPJACL_|bIMMXwN zMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuyP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7 zSXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?WjVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@ zX=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2Ta&vQYbaZreb#-=jc6WDoczAeud3kzz zdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyDgoK2Jg@uNOhKGlTh=_=ZiHVAeii?Yj zjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z}m6ev3mY0{8n3$NEnVFiJnwy)OoSdAU zot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5(rl+T;sHmu^si~@}s;jH3tgNi9t*x%E zuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#pxVX5vxw*Q!y1To(yu7@dCU$jHda$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD) z(b3Y<($mw^)YR0~)z#M4*4Nk9*x1lt)=I7_<=;-L_>FMg~>g((4?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa z^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg={r&#_{{R2~G;g-C00001bW%=J06^y0 zW&i*HuSrBfRCwBrli>}8FbITO;$AM{8YGND;u;S5%uAbcK!g3E)$fGELTkn9_Cdy7 zbAg!Gng|3UYY>Rbtxh1poq}NS`+sGtpvW<4kDMV(cbwbxfRFCEcY4h2;^6d+ASs^; zrMBU7Lo46nY^+emtyu!oNhM@q;$)Qx<=r6yr%uLMNcuhdKgk=Xge`g@S3K^QpUt;S vw+)l$a~4ryjk{@RsWblP)tW#BR%}00000NkvXXu0mjfmJ|7= diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/StepikRefresh.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/StepikRefresh.png deleted file mode 100644 index 947919a7f48c61bd3dadf1a79e8b21347cbe8647..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 382 zcmV-^0fGLBP)0`k);O2`+@ij8U~4}5v_U7`I?Kn?=8Cr!c$KdpC@PtvgSpXTCY6+a{tqtZ_mDh z@THeWUf^{JF3krYu6ua=$=1h6F2HUGHqBda&$|s018D{sbmPtG*Jq#aeFpLx%n)P) zzzVTxzWe^tTbNfs7^DW~A`}Banm`IbPDa;!_4V;H8*k1G0MBi7eTy*VT5FbkOgK%k|Y*_VE4ei zFx=*Tybo#9z`*;s%sDf2X1KZBzj@kbv{n(`^7P7If@e!2A$T?t(vI z7-GdDfKny}$JxnjJabKml>k+I9B{M&-^spcDnEZZKJX@h?WTevkT1CWUlYEXWb?5E zFz2p?M8HdW+?9HNVDPF}3n&0G2O;_om5^m;v-Se#M2Ox~AP7tUM!-uFtROse5D1+u z;Kool0$wBF3Z(3)K*4JfVTtHjD`6_Nqx2$RUU-)SpqZ0roXvY*Jg`=w3si-hXAvwQ z!HY}_s`9x=&IPG%Ka$9f4BU`GBSwT(?LCrb8;P%ASWIY*7}vU1=D;=Xv|5SofeKv8 zp}77T diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/Task.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/Task.png deleted file mode 100644 index b678c649b30357445f388ed1cfdd804fa0080e86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`>7Fi*Ar-fhfBgS%&#Wr3e#z%0 z%>Nx{WH2xDo6B`<+FNV>+uz^)d~aB4FVTEM!ElC+9rHit!dCC!$9{j?YyHpK{|@Uh z1+#!Rj2?VT+7>Xf7&tO>B;ArO`1r_mO~n<~FC_;LNILMbF$BH1lTfB-BMP*V!PC{x JWt~$(698!nJJSFF diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/Task@2x.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/Task@2x.png deleted file mode 100644 index 4abb95ce7c9eac5729e899e97678807d5e79caa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJE1oWnAr-fhfBgS%f7l_ZP4P8D z`@g^Q&Q0dCVNMj7{f@6-Zq0+4_tzZ$?1jIvA(UiF4_qie(D721nc*pqY_ax{vzkEv OF?hQAxvXp0jd!h4wd@iUCpO!q)V_Nu9AUhF=mjQ8o^|Xcp%_S~R4L17y z*IDH`4WA3DO&0mwR$gHLKXhgKN+3V6Z)$(4-dy+F+Uvak>u&bntG3jI1+NR77KCq5 zUE}^=ak2e>`)NUIO=o%?P+sBuUwxz3Z6GZ`C=lm^obI$NYJ<`m=l}9c?fxq)v;VKU z+VvZdRwXtTz=qf_3Ed#S&i=prTKm7zb2Gc~MI%luz09;hYN<&UdS(IuV7Qgwi5-#5 P00000NkvXXu0mjf+@PqQ diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskCompl@2x.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskCompl@2x.png deleted file mode 100644 index d657aa68f7dfc63bc415d985e9ea83e71ecff737..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 822 zcmV-61Ihe}P)w55etN{eN(1%jy2kQft88e?1#S3)#FYZ%);s5KD6 zAXA}*4wRvk$Wk}NjT((BS+X%k-SGSFvo(z|8ahBsSbWLJz31G1@7(w9P169R9Vco3 ztpGI;HfXKedjt|6xq!~Lz1Qg60fEFR6;M1{+fhAPM--}m6SX5@n8sePkeH0c=8&3% z1ZbN6g}QeQTI(jNZ9mb|As8H6sB?x{NKD3}^CXwl#I8X&tNP*xsUfv7 z>%=dhee8Kp^{wJCFf|{NCD0RDC?0x}A&@Li1QHY2-Q7JDCmlF>3I8ULGpuN1=sK1y z$Qe;`#7@;d1DLq$NIM{;<>_LlI#e*NgLI|_d1IA0st&wm1%?%OB@Vy`hF0rKPKo3`711y;=)jn(9j zTtL4|O!U(OclZwFZr+R@CBYWRwk|`)2o(8DjzqS=^1$Ca$g!1zJ1j>QEko1P4L>t( zW{66>B2p@QV@NnoA%CS2#T%_C30*?*X6v@ZU4JT(4p=01%{%^>8p7vQ$ey6U87V{M z(8btJ+`DeOHN<_YI1n$?AU|k?bfX#4^`?&!f8&`%KSN84t0N#`%vYnN9Xz%Id(~A9UW<1wd56PMll6BJ;CSRJ2gNQt#xn{mSkQ-zKR_h`1)POso zMuz>Np0q$}oq6pkopF5x3?~3O(jGHXC zvOCMeY=NizJIAG<;`(EfD4ZpEcc!bX<6n6{07N^~!a>4zFaQ7m07*qoM6N<$g1pXj A`2YX_ diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskCompl@2x_dark.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskCompl@2x_dark.png deleted file mode 100644 index 3bb92e6bde0ad152d169387de72d15bb015347ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 821 zcmV-51Iqk~P)?H2ndJ{@n4C6pf4a8{vkx#Adont0*ap1ZPmjy#G(2lXc&pYJpP=8#AGZnht#Ab zK-2Uu)V-_FIU=ZY{=`6Uz&N^zMt78j#AGZ!PjX33;u?hg3Lu8HrZ>#Ad3|4y8d96E zPVxe}2fp`I-wO5vQ}ZER0^PBNlA${t0_oyNAT8R5$Rh2KM=zq>-;$C5`El!2=Z!w8L-R)CFb887 z(C-qH{q(>s{;2X*(X;^-VGCrN7a(H<$^z!mRJOogebX?=aaMvmEJq$KgS4wA$c$SV z;u0^3l*(n=5Kd4iUN)h8y$ux`=TRPM+md*jj-}E8yTqk=#~)Wi_)LrZNeY~iD%1|0 zP3**-E6(df+((K%@q9gs!!0OTZ$-)4xsQ^d=|rlZp}n+68xk!rZ*#C2;-v=gpHj#j zu7u=nH7jx7)!oUb6}$P&cwwLklGPSS*34g+{E~DWMC1vr^>ZDe!Z0JS(gcyO9^6Sa za$KjN9&8Sl4e4HR#*`2)8rXRzZ%-U-Y)$7$$VwRIJ34K z_EL!N*MYyR1<$Ji&!=HyiO=|n>!~@O>C1?f(9X1nxJ$K&Bk*cJcRs$?mCS9JG#)w*|wHCOI&_xFLd6I&jr=t(~>88ObcHMWG4dgG9a$6p4L#Hxy0qE!A8IT zI;%XV;d4Q?`ZW97$_woO>o+^D1o9L6ruL`m&2_)6z0Ui;?q>hJYD-;M@VdZhLHGvM zHSYfv7u)~0pBA*%bf(t<54m&<*nI?ElNJwf`GEH?tdGG~&e4%S;=jmYQUtXC?q3RDVxu(x;68 O0000Jf`k*?Yc zQgJIep@HF;iNMclTj7ph#SM*}jt&kDjx!j~q;s@h@{*ph;8o;-CwoIU{+mqr&*(9~ fbwzSvf&)8)?jp`uQQkepAS*mw{an^LB{Ts5GTBGg diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskDescription@2x.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskDescription@2x.png deleted file mode 100644 index f136a2a782c8c67cdc389caa6f08c3a519a4343c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI13?%1G+4BcTv7|ftIx;Y9?C1WI$O_~$76-XI zF|0c$^AgBWNcITwWnidMV_;}#VPNg0q2elmhC%TCm(R=-|sx>0FVomvFq7k+p{wyB*dY2yX)lL&Xe}obnSAP zyvJ?oUWdLto-_7&P2cZ5Yk%OpLw>Uk15LBI*t!*HI9ExKUoZnhTh5)s&pe;qmEE)s z$YWqk@^*J&=wOxg0CG4BJR*yMG>GlN;C3?G4#)`gba4#PIG>!b!0f>xE(uA`X-P?G z$5fA=Idt|g*VIR>PAW|^`{Nx1mQN9g3_7G_vDD@0jT?m?o!%Zr6ZBe-JJ_64I3NF^ z!HGZ4&*g)Vyr#<$qpge#rxuCWyjvrF3TT09iEBhjN@7W>RdP`(kYX@0Ff`RQu+TL! y4KXsXGBmIpMMG|WN@iLmZVjB>cNBmc7(8A5T-G@yGywoGo}GmN diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskDescription@2x_dark.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskDescription@2x_dark.png deleted file mode 100644 index 3ce9b0bce973fe42de29fd1aeb55ce9396f9a5ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmeAS@N?(olHy`uVBq!ia0vp^QXtI13?%1G+4BcTv7|ftIx;Y9?C1WI$O_~$76-XI zF|0c$^AgBWNcITwWnidMV_;}#VPNg0q2elmhC%TCm(R=-|sx>0FVomvFq7k+q3iH#fuKT+g&H`cAm7yrfZkW zGYx;igS^ER$9rBxX7-*WrvI#&-7`RG;{DK)6+H&q3e&+e?uI#3D zKpq2QlDE4HLkFv@2av;A;1O92q(N*C2Dg*hc0fj`r;B5V#`)xg1!fNpaY;yePD@Hk zJEnT{%%QV~xu!m1by8`X*&pv9uzZR@WY8fci={3{Z`>&K==Am|nxNNu+`;CY!uj|Q z4Nm-Vel8z`A#kG_nC}Q!>*kacki0zM}xtz~JfX=d#Wzp$Pz!MxLJl diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskDescription_dark.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskDescription_dark.png deleted file mode 100644 index 4bff140d3e417ef354d67e61e65d5f91e717b06a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 zcmeAS@N?(olHy`uVBq!ia0vp@Ak4xHBpZ(Y^aoM~0X`wF|NsA=3I;Ci8!g*+0!bk6 z;>C;hy*uoC_c%}7hY$>3Kt*47) zNX4z>ga(FVCIUaJZG}626*n|?IyyKsIL=@^lg`n4$xC|1f>)6Tp6m_b_-``dKcmO| f))mQx2@dQGx{EktMS1rWgRJm$^>bP0l+XkK!Wc-; diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskProbl.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskProbl.png deleted file mode 100644 index 6173d6423385365a0c0e23fccd2a1f7a2f3430ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 336 zcmV-W0k8gvP)bI#qnd260U#R0zmyzug%}qUT+533^PoKK|D&o*hyg$v#J`>y`I8s}fG&uA zcE2R~|AW#LI0o^rXG9R?f)(2<3g+I;iT!^+KOXD?5DnsANeB$bnwhZSYl%VOw=*NJ i-N}mj1)@QGkRAY5C%KtI_waB40000M^Cao1hzhA2&^S5BrN zXehr#L0fY!l?e#)^gbtW2k0X7plkEuvpsy@@AL9K&wI|<4i5l$7YXmb4N&rWieEZB zy@gBOEcsH>G5(Ez>wPLG#I!Dz!jGP&KAIKx97#>1TJsFi#`G?=`p2VMA{VRGJ$RU z<9CBEo=&P|mbvS!5rJ`^QAx<&}}G{a`E29mKTBohgyMh(}{W2GttTn4a? z5B7IEl%j~QLjjxmRtWWJ7>>jHa2z>2`HgGnaVms+^CEsRS4Uv7>2+qeMg|+*Gz9f6 zCnR=vAn|KALqiXG7o~R$Tus2RsG@@YPIC1CP6(Ae4iMUrF>RZdv(Ys1Z4mdl4rDkbx zy}cgRbqyKpd>CiQ*8RlmQq|hI3SgRPQ<}Yx9)Q11or|k-6Y<#i7SdZeG!=-YsNfkD(P#kgESG3i-E17XIWMy_~#Fk zd{~iJReMym`(8B#f5T1Ih(}P+uVaU#vdBfwcTFL5c$A_ew^9@)^#akJ;Tqff8;yxO UC*r2X)Bpeg07*qoM6N<$f?mr|B>(^b diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskProbl@2x_dark.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/TaskProbl@2x_dark.png deleted file mode 100644 index 2eb7b2d42c2fc46cae97552eede8a4679022ba45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 739 zcmV<90v!E`P)i0oc~K(H8!gaH8o*^9H9V%fP;qbmCk7!Sz2u}R&z!h-&$WEM?R zjc*`WpLu}mrtyu+;YmQSE@*&DPwbPIpM8cbyceTT_R0abEE5b^qsI1N~K&E`X{ z4BDY|XcVrQ{&2%Q2K?7wp?uiEfHi8_b@XBndk-Yv0FZ&cI{1l<`Ac445B9S5IeCE6 z{tsT>a~tFX>G5$Dz?j+SBpA5}z!et(P8UEZ6z0Xn1TJpE^F9J6>9J;BL@gQ4WC9WV z<2M6OAC2kc_9Y# zcG2}Prx0vUOUea>n1JC-*EUHA?(_(%JS4UudPgUDI205&C(Gcos zJS4TX38`OOIU0J^;`(>Wx5gdy=Wa!g0>qN;YDs|mEYw36g% zryABCYI2ANeXt)29@Ek5)=Kudnm~)2mL`Mbs)A%=HZhM2;{KImI(nDs`d!YBU|uf^ zt+py*wXGqC&36`tqk4dN-I}^@Tm`UBG^=gC`*$GFtjHl`>|{8qqjyOuCbCQY@PG3Z}2Zj-^j!OIWQpghXx*6CSdY0rhf`9%n z$p@FBM(EYl?f7&Q{0$FTBOXCPzm6B`D~sLad{yKKiuc$0HQ(sOUWTuh;c!|wXCQ=56aX2KdQ=v7yzU}{Og&KKZ!A*?NUzc zv->5<{~wg5z%ht_JtKlB7p&M`Q84#zPVE2t`SD;EfM^i^NNx{WH2xDo6B`<+FNV>+uz^)d~aB4FVTEM!ElC+9rHit!dCC!$9{j?YyHpK{|@Uh z1+#!Rj2?VT+7>Xf7&tO>B;Ar05E5z24iG&Y(^@dmC45h0w%MONF)L;paDrB5EM$~qTVzxr=yG21M@nC&Lo?gr&6h9 zWo1s9lS!dahK7c$c8D>#oWqqD^v(6p=~vdw0|PuVnG84u1qIdB)gqAy0)QP?IkGLA zT{toErk<**s>#Vo;9NFsv8P|>Kasy$-Gm_sJancQ9UZj;z;K^jpVcV^>YF-CN=mxA zx~x{K!{OMnLw^XDfD53sw3Nf))C-qvHXE1A1*$WYKp^Oq>Bh&$NhA`W0VAkwYHA9G zp+)1y)yK0i3FmBi# z4#KYu{*g(3;R&B5UrMZGWOdGh=H{`7Zq+s3t7n|VWCTPe1VnuGdFthqhkajnNb#NW zcrxt-Iwj^D{x}L3gv10PlY-;1C<-m3U7ARyg`shwiP$i7ayVE?xQsR_7@-vQU)cLS zaap;sSX?YF1xpttk!k4cTy$z$5>|MIiRY z=8@mQa~9U;cYMOsK^v=j{i?;@v{PhX*BVR)jc(ogd>2j%1`2|$hGnB2E3+HSFr+rP zoSK1FR9eXPS~U|eXi%vPu#xXiwXUgEYI8l^wq{bVS_6j-Mg#mT5->G3l|FgSzG8sX zHn%jn&A4pX-pEO-S*tOcv^ulvkrut(V$|u37W>|;9>u2j_f@sHV0Q$hjeoWjQUivI&f5&+- z-|b@Ny%ImcI_j8~Ezy-v2p@iDc<`Wz=<~s*E$WMqC|}Qr=qUFks;9y$zu0SunDy4> zD)C+nGVPa%5uSgD`-inh5B|yXQF;3P_9`B(-Qm{g-_R6wDl|Pc7|Eoyo<40}tIXE& zGXFlBYV7kHYQNHwkMD4(G&z|$yPC5vLbkJ?9=m?RbRl6ndP2@QlItVdcSz-a|9G(Pf#I#G(DX1-E_7h}N7}(d Y7n=CrE~o=9d2i2ETns9@H4;zw2djw6#{d8T diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/interpreterGear.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/interpreterGear.png deleted file mode 100644 index ea7f94761fdbc648942f9da81340c44fe4f2dda9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 316 zcmV-C0mJ@@P)Vps3Hr(dIA@y$_d7jV{MPzj6HE2JLcr7A(N-( zN?9&B&8gBf9sFhh<;XQkb%iseE(yl!3Z>yEN#Zmgaou>tyP#H{Saof{l@)zj{kBzXR-`%&^#mYHRBo3$l_ z6atAPFH2{4-ptPIY^RCD|L+gKMx*giuh#=$#Ry1tN(Y|Ufh|ZD3%T$6Ng7+NmI=}_ z$O}6npgQm?0;CI+9mg^0+4H=l$O$MJAnahA0(}tVwfVO00Lj!fq(i#Q(ZO?CWV~9f z-m>wzLf(U{gwe+6VH|}xFyc9z*Y=dTIb<)jTFsQ?m%=_{#kE2*0T#;hI^zF=*Xnaq zaNkQDC~)Y4n9(P0=_$`^Av_e6!u(#<042)fXAH)Pqa-$LSyqyxU_-H+O_53Hc-N%ws#E)Cf!99mp!WS(Ys$bVkg%wc)z%mF_^W zW5+5!fK12+GCU{Y2;e|UCs+w{+T9Vwu8?DeYx(Bv95`tT;6UoU4q-?c`Z1QFFRuS$ zu^9VBL|iKOsjuP*9I*C!51@I-9pPn^lU~6`dmUqAcaqRoj?|FMsM!&40}mJ*Kf&TMi|Y({X59UTHR)rpkTZ qA10UbX?6{xX&}W&lk6ftbbkO$3hGZR{%<<~0000TTfBM1v|fhU+jy$7B`w){B!1(bmWgbToy=p}ULR`5MjOw$yo zFrXxoytMS|_xjqeoqpZh`~R)*t5&ONU|OkE-U-&(woL&UA>a!_K*ksHe!m}J*=n_{ zTCJ7@aZK%l_OvR1Z5p95*prP$!{Yy5uNN?#VHkurD2E_;sMqUJ#h#rFE{Vv>edt49 zUpfWK{La(Ku&U-?t!SXgzFBTC+Ck&0~{H1&j$e> z29FyL>^M$9gdzdw7x}oYFmsmN6A78yGz_+ebhg>vfExPT5v+?gb#u;y*d|MU30#RX zJLjm8>=n32{aErnW*cpDR)btw*vXv%E zcsGn|Zb8^`OMJgW2myyH2>>=Gz&Hf1>#iGuNvG2Z{>mogwT*hZ+wJZzJBE&r$Ivl8 z|F&)W{vjfl%)RYTaT@~8esm8YnK*%C-pAW8Pfnx3H+3D0%<9zS4oCqb5kKD&5+*rJ zc9se#6bekqZUJpd2Jmz|X*S#wcRRij+LVlG9@9OeFqF&XFhz*e%v~cXb;%(T+_2H$ zgTY{S3@tdPOw)4R!$hRzsb;fj$tnNeewgga)9e5UVelZqI@@;gq5BEY3-BFQwxHzz O0000yc5s}f zR+^@6k|Zrbd$yn~%N2+|3OxFGp0DBe1Z~EcBZu5k10(|^U-&BE3{*fbs3fL@<4=w3 zA#pc~Y*2d2sVO3Z*a05_?Ou))jSAy9-hi+LE>ipjo*;(=ZN{X*6?d{@jk$9>7?Zn( zMn067$!Y)Bfa4GtuQHDCfPWxpON>XTG=J(PPJ>H)3pyPK=V*HYibTylBRDrcxlKro zXn~@sjlx=D%r%ojDvE+q4v^f_&$7(bQl_oNW|i`Au}J?+y1^pr2QF)n%VUfU82|tP M07*qoM6N<$f|y2s5C8xG diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/showHint.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/showHint.png deleted file mode 100644 index e3d4abf91fe9cd0d2a5851ae9461e7f4952b143a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410 zcmV;L0cHM)P)|k<1xiozek3S5b&@b3cc(`$k`j>@(MHLUB-X*$XG`f0?1mF ztZgOvsz=DqGSKM+Ir(YsF+&#k4gk1Y>1lr6=_5Ncz^)vlvN6UzW-Mz7e2D-+nx;`L z@jM8?O5a)*sBQoWR5xIaY2%kx{{om~vVC8MpQ;7;0%VBF&=2lo&j0`b07*qoM6N<$ Ef>uGang9R* diff --git a/python/educational-core/resources/icons/com/jetbrains/edu/learning/showHint@2x.png b/python/educational-core/resources/icons/com/jetbrains/edu/learning/showHint@2x.png deleted file mode 100644 index 1d79e73b99ed3806f0834f91bcd9340810427494..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 871 zcmV-t1DO1YP)qs`)-{g~hynr0zg#uWkh8|`x3k_?H@Yqs-snY#BX0FE#&)-j^ z)fVOE#@O`jpTP`f4J{c2Ej-{QJHT|=!T7m53AFMoiw*gSNBK#|f`n(GffgR{k{v)- zexzDoqae#5j(5{k7Oa659_&>mJHY(C#LvdoJhHwjB-`tPEdeb&;3Yf2Le(joshf>P zfQraDK+ZXhum)J87O1}YyMW*UFWCW>>lAKF-#M}ks{|bH)Z{YMlJkp3z_nUW_K70q zBDstWV%=?QB*$d2sDlT4k?a75#B?G#${IE}!2}tYSdDcD%`8qb#pW10^Q)87>}GPf)Ju#Q|1kk&EF8bwH-WWE9PkCUR;oB0C-A$-^vIxirtW0p0$ z!WO`bTCF}245B6+0ZK|rypT-79w6uw@Xr4~fq(7<+%h)*z8wJuWds=5jsVfjSP+|! xd1k}^5;BoYGSkPHd^TNXx;(weu)c)b%?~VJR7>W<8bSa7002ovPDHLkV1lQloy`CM diff --git a/python/educational-core/resources/oauthResponsePages/errorPage.html b/python/educational-core/resources/oauthResponsePages/errorPage.html deleted file mode 100644 index 729896876db5..000000000000 --- a/python/educational-core/resources/oauthResponsePages/errorPage.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - Stepik Authorization Succeeded - - - -Authorization failed. -
- -Couldn't find code parameter for Stepik OAuth - - \ No newline at end of file diff --git a/python/educational-core/resources/oauthResponsePages/okPage.html b/python/educational-core/resources/oauthResponsePages/okPage.html deleted file mode 100644 index a56a5a7791a6..000000000000 --- a/python/educational-core/resources/oauthResponsePages/okPage.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - Stepik Authorization Succeeded - - - -Authorization succeeded. -
- -You may return to %IDE_NAME - - \ No newline at end of file diff --git a/python/educational-core/resources/style/buttons.css b/python/educational-core/resources/style/buttons.css deleted file mode 100644 index 6e7d822eb891..000000000000 --- a/python/educational-core/resources/style/buttons.css +++ /dev/null @@ -1,22 +0,0 @@ -.radio-button .radio { - -fx-border-radius: 15px; - -fx-padding: 5px; -} - -.radio-button .dot { - -fx-background-radius: 15px; - -fx-padding: 4px; -} - -.check-box .box { - -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color; - -fx-background-insets: 0 0 -1 0, 0, 1, 2; - -fx-background-radius: 2, 2, 1, 1; - -fx-padding: 0.25em; /* 3 -- padding from the outside edge to the mark */ -} - -.check-box .mark { - -fx-background-insets: 1 0 -1 0, 0; - -fx-padding: 0.333333em; /* 4 -- this is half the size of the mark */ - -fx-shape: "M14.596,2.055L12.455,0C9.687,2.884,6.011,6.714,5.158,7.602L2.055,4.624L0,6.765l5.242,5.037l0.003-0.004 l0.003,0.004L14.596,2.055z"; -} \ No newline at end of file diff --git a/python/educational-core/resources/style/buttonsDarcula.css b/python/educational-core/resources/style/buttonsDarcula.css deleted file mode 100644 index f287398222a6..000000000000 --- a/python/educational-core/resources/style/buttonsDarcula.css +++ /dev/null @@ -1,64 +0,0 @@ -.radio-button { - -fx-text-fill: #bababa; -} - -.radio-button:selected .radio { - -fx-border-radius: 15px; - -fx-padding: 5px; - -fx-background-color: #4f5254; - -fx-border-color: #589df6; -} - -.radio-button .radio { - -fx-border-radius: 15px; - -fx-padding: 5px; - -fx-background-color: #4f5254; - -fx-border-color: #323232; -} - -.radio-button:selected .dot { - -fx-background-color: #bababa; -} - -.toggle-button:selected { - -fx-background-color: #3c3f41; - -fx-background-insets: 0 0 0 0, 0, 0; -} -.toggle-button:selected:focused { - -fx-background-color: #3c3f41; - -fx-background-insets: 1, 1, 1, 1; - -fx-background-radius: 2, 2, 2, 2; -} - -.radio-button .dot { - -fx-background-radius: 15px; - -fx-padding: 4px; - -fx-background-color: transparent; -} - -.check-box { - -fx-text-fill: #bababa; -} - -.check-box .box { - -fx-background-color: #4f5254; - - /*-fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color;*/ - -fx-background-insets: 0 0 -1 0, 0, 1, 2; - -fx-background-radius: 2, 2, 1, 1; - -fx-padding: 0.25em; /* 3 -- padding from the outside edge to the mark */ -} - -.check-box .mark { - -fx-background-color: transparent; - -fx-background-insets: 1 0 -1 0, 0; - -fx-padding: 0.333333em; /* 4 -- this is half the size of the mark */ - -fx-shape: "M14.596,2.055L12.455,0C9.687,2.884,6.011,6.714,5.158,7.602L2.055,4.624L0,6.765l5.242,5.037l0.003-0.004 l0.003,0.004L14.596,2.055z"; -} - -.check-box:selected .mark { - -fx-background-color: #bababa; - -fx-background-insets: 1 0 -1 0, 0; - -fx-padding: 0.333333em; /* 4 -- this is half the size of the mark */ - -fx-shape: "M14.596,2.055L12.455,0C9.687,2.884,6.011,6.714,5.158,7.602L2.055,4.624L0,6.765l5.242,5.037l0.003-0.004 l0.003,0.004L14.596,2.055z"; -} \ No newline at end of file diff --git a/python/educational-core/resources/style/javaFXBrowser.css b/python/educational-core/resources/style/javaFXBrowser.css deleted file mode 100644 index ec00af2078a8..000000000000 --- a/python/educational-core/resources/style/javaFXBrowser.css +++ /dev/null @@ -1,20 +0,0 @@ -.example { - background-color: #c4c4c4; -} - -a { - text-decoration: none; - position: relative; -} - -a:after { - content: ''; - - width: 100%; - position: absolute; - left: 0; - bottom: 1px; - - border-width: 0 0 1px; - border-style: solid; -} \ No newline at end of file diff --git a/python/educational-core/resources/style/javaFXBrowserDarcula.css b/python/educational-core/resources/style/javaFXBrowserDarcula.css deleted file mode 100644 index dd455079e687..000000000000 --- a/python/educational-core/resources/style/javaFXBrowserDarcula.css +++ /dev/null @@ -1,43 +0,0 @@ -::-webkit-scrollbar-corner { -background-color: #3c3f41; -} - -body { - background-color: #3c3f41; - color: #bababa -} - -p img { - background-color: #bababa; -} - -textarea { -background-color: #3c3f41; -color: #bababa -} - -a { - color: #589df6; - text-decoration: none; - position: relative; -} - -a:after { - content: ''; - - width: 100%; - position: absolute; - left: 0; - bottom: 1px; - - border-width: 0 0 1px; - border-style: solid; -} - -tr { - text-align: left -} - -.example { - background-color: #4f5254; -} \ No newline at end of file diff --git a/python/educational-core/resources/style/javaFXBrowserDarculaScrollBar.css b/python/educational-core/resources/style/javaFXBrowserDarculaScrollBar.css deleted file mode 100644 index 3527a3d23da4..000000000000 --- a/python/educational-core/resources/style/javaFXBrowserDarculaScrollBar.css +++ /dev/null @@ -1,56 +0,0 @@ - -.scroll-bar{ - -fx-background-color: #3c3f41; - -fx-background-radius: 2em; -} -.scroll-bar:horizontal .track, -.scroll-bar:vertical .track { - -fx-background-color: #3c3f41; - -fx-border-color:transparent; - -fx-background-radius: 2em; -} -.scroll-bar:vertical .track-background, -.scroll-bar:horizontal .track-background { - -fx-background-color: #3c3f41; -} -.scroll-bar:horizontal .thumb { - -fx-background-color: #bbbbbb; - -fx-background-insets: 4 0 4 0; - -fx-background-radius: 2em; - -fx-opacity: 0.2; -} -.scroll-bar:vertical .thumb { - -fx-background-color: #bbbbbb; - -fx-background-insets: 0 4 0 4; - -fx-background-radius: 2em; - -fx-opacity: 0.2; -} - - -.scroll-bar:vertical:corner-present { --fx-shape: " "; --fx-width: 0; -} - -.scroll-bar:horizontal:corner-present { --fx-shape: " "; --fx-width: 0; -} - -.scroll-bar-corner { --fx-background-color: #3c3f41; -} -.scroll-bar:horizontal .increment-arrow { - -fx-shape: " " -} -.scroll-bar:vertical .increment-arrow { - -fx-shape: " " -} -.scroll-bar:horizontal .decrement-arrow { - -fx-shape: " " -} -.scroll-bar:vertical .decrement-arrow { - -fx-shape: " " -} - - diff --git a/python/educational-core/resources/style/javaFXBrowserScrollBar.css b/python/educational-core/resources/style/javaFXBrowserScrollBar.css deleted file mode 100644 index e8e05f85c3b2..000000000000 --- a/python/educational-core/resources/style/javaFXBrowserScrollBar.css +++ /dev/null @@ -1,51 +0,0 @@ - -.scroll-bar{ - -fx-background-color: #ffffff; - -fx-background-radius: 2em; -} -.scroll-bar:horizontal .track, -.scroll-bar:vertical .track { - -fx-background-color: #ffffff; - -fx-border-color:transparent; - -fx-background-radius: 2em; -} -.scroll-bar:vertical .track-background, -.scroll-bar:horizontal .track-background { - -fx-background-color: #ffffff; -} -.scroll-bar:horizontal .thumb { - -fx-background-color: #dddddd; - -fx-background-insets: 4 0 4 0; - -fx-background-radius: 2em; -} -.scroll-bar:vertical .thumb { - -fx-background-color: #dddddd; - -fx-background-insets: 0 4 0 4; - -fx-background-radius: 2em; -} - -.scroll-bar:vertical:corner-present { --fx-shape: " "; --fx-width: 0; -} - -.scroll-bar:horizontal:corner-present { --fx-shape: " "; --fx-width: 0; -} - -.scroll-bar-corner { --fx-background-color: #ffffff; -} -.scroll-bar:horizontal .increment-arrow { - -fx-shape: " " -} -.scroll-bar:vertical .increment-arrow { - -fx-shape: " " -} -.scroll-bar:horizontal .decrement-arrow { - -fx-shape: " " -} -.scroll-bar:vertical .decrement-arrow { - -fx-shape: " " -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCAnswerPlaceholderActionGroup.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCAnswerPlaceholderActionGroup.java deleted file mode 100644 index 27e915eec5c9..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCAnswerPlaceholderActionGroup.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.project.DumbAware; - -public class CCAnswerPlaceholderActionGroup extends DefaultActionGroup implements DumbAware { - @Override - public void update(AnActionEvent e) { - CCUtils.updateActionGroup(e); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCHighlightErrorFilter.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCHighlightErrorFilter.java deleted file mode 100644 index d3e9e17598d1..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCHighlightErrorFilter.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.codeInsight.highlighting.HighlightErrorFilter; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiErrorElement; -import com.intellij.psi.PsiFile; -import org.jetbrains.annotations.NotNull; - -public class CCHighlightErrorFilter extends HighlightErrorFilter { - @Override - public boolean shouldHighlightErrorElement(@NotNull PsiErrorElement element) { - PsiFile file = element.getContainingFile(); - if (file == null) { - return true; - } - VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null) { - return true; - } - if (virtualFile.getPath().contains(CCUtils.GENERATED_FILES_FOLDER)) { - return false; - } - return true; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java deleted file mode 100644 index 4e0e4e7bb726..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectComponent.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.AbstractProjectComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.jetbrains.edu.learning.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.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class CCProjectComponent extends AbstractProjectComponent { - private static final Logger LOG = Logger.getInstance(CCProjectComponent.class); - private CCVirtualFileListener myTaskFileLifeListener; - private final Project myProject; - - protected CCProjectComponent(Project project) { - super(project); - myProject = project; - } - - public void migrateIfNeeded() { - Course studyCourse = StudyTaskManager.getInstance(myProject).getCourse(); - if (studyCourse == null) { - Course oldCourse = CCProjectService.getInstance(myProject).getCourse(); - if (oldCourse == null) { - return; - } - StudyTaskManager.getInstance(myProject).setCourse(oldCourse); - CCProjectService.getInstance(myProject).setCourse(null); - oldCourse.initCourse(true); - oldCourse.setCourseMode(CCUtils.COURSE_MODE); - StudyUtils.registerStudyToolWindow(oldCourse, myProject); - transformFiles(oldCourse, myProject); - } - } - - private static void transformFiles(Course course, Project project) { - List files = getAllAnswerTaskFiles(course, project); - for (VirtualFile answerFile : files) { - ApplicationManager.getApplication().runWriteAction(() -> { - String answerName = answerFile.getName(); - String nameWithoutExtension = FileUtil.getNameWithoutExtension(answerName); - String name = FileUtil.getNameWithoutExtension(nameWithoutExtension) + "." + FileUtilRt.getExtension(answerName); - VirtualFile parent = answerFile.getParent(); - VirtualFile file = parent.findChild(name); - try { - if (file != null) { - file.delete(CCProjectComponent.class); - } - VirtualFile windowsDescrFile = parent.findChild(FileUtil.getNameWithoutExtension(name) + EduNames.WINDOWS_POSTFIX); - if (windowsDescrFile != null) { - windowsDescrFile.delete(CCProjectComponent.class); - } - answerFile.rename(CCProjectComponent.class, name); - } - catch (IOException e) { - LOG.error(e); - } - }); - } - } - - - private static List getAllAnswerTaskFiles(@NotNull Course course, @NotNull Project project) { - List result = new ArrayList<>(); - for (Lesson lesson : course.getLessons()) { - for (Task task : lesson.getTaskList()) { - for (Map.Entry entry : task.getTaskFiles().entrySet()) { - String name = entry.getKey(); - String answerName = FileUtil.getNameWithoutExtension(name) + CCUtils.ANSWER_EXTENSION_DOTTED + FileUtilRt.getExtension(name); - String taskPath = FileUtil.join(project.getBasePath(), EduNames.LESSON + lesson.getIndex(), EduNames.TASK + task.getIndex()); - VirtualFile taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, answerName)); - if (taskFile == null) { - taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, EduNames.SRC, answerName)); - } - if (taskFile != null) { - result.add(taskFile); - } - } - } - } - return result; - } - - @NotNull - public String getComponentName() { - return "CCProjectComponent"; - } - - public void projectOpened() { - migrateIfNeeded(); - if (CCUtils.isCourseCreator(myProject)) { - registerListener(); - EduUsagesCollector.projectTypeOpened(CCUtils.COURSE_MODE); - } - } - - public void registerListener() { - if (myTaskFileLifeListener == null) { - myTaskFileLifeListener = new CCVirtualFileListener(myProject); - VirtualFileManager.getInstance().addVirtualFileListener(myTaskFileLifeListener); - } - } - - public void projectClosed() { - if (myTaskFileLifeListener != null) { - VirtualFileManager.getInstance().removeVirtualFileListener(myTaskFileLifeListener); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectService.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectService.java deleted file mode 100644 index b009d097b52a..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCProjectService.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2000-2013 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.coursecreator; - -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.editor.Document; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.util.xmlb.XmlSerializer; -import com.intellij.util.xmlb.annotations.Transient; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; - -import static com.jetbrains.edu.learning.StudySerializationUtils.*; -import static com.jetbrains.edu.learning.StudySerializationUtils.Xml.*; - -/** - * @deprecated since version 3 - */ -@State(name = "CCProjectService", storages = @Storage("course_service.xml")) -public class CCProjectService implements PersistentStateComponent { - private static final Logger LOG = Logger.getInstance(CCProjectService.class); - private Course myCourse; - @Transient private final Project myProject; - - public CCProjectService() { - this(null); - } - - public CCProjectService(Project project) { - myProject = project; - } - - public Course getCourse() { - return myCourse; - } - - public void setCourse(Course course) { - myCourse = course; - } - - @Override - public Element getState() { - if (myCourse == null) { - return null; - } - return XmlSerializer.serialize(this); - } - - @Override - public void loadState(Element state) { - try { - Element courseElement = getChildWithName(state, COURSE).getChild(COURSE_TITLED); - for (Element lesson : getChildList(courseElement, LESSONS, true)) { - int lessonIndex = getAsInt(lesson, INDEX); - for (Element task : getChildList(lesson, TASK_LIST, true)) { - int taskIndex = getAsInt(task, INDEX); - Map taskFiles = getChildMap(task, TASK_FILES, true); - for (Map.Entry entry : taskFiles.entrySet()) { - Element taskFileElement = entry.getValue(); - String name = entry.getKey(); - String answerName = FileUtil.getNameWithoutExtension(name) + CCUtils.ANSWER_EXTENSION_DOTTED + FileUtilRt.getExtension(name); - Document document = StudyUtils.getDocument(myProject.getBasePath(), lessonIndex, taskIndex, answerName); - if (document == null) { - document = StudyUtils.getDocument(myProject.getBasePath(), lessonIndex, taskIndex, name); - if (document == null) { - continue; - } - } - for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS, true)) { - Element lineElement = getChildWithName(placeholder, LINE, true); - int line = lineElement != null ? Integer.valueOf(lineElement.getAttributeValue(VALUE)) : 0; - Element startElement = getChildWithName(placeholder, START, true); - int start = startElement != null ? Integer.valueOf(startElement.getAttributeValue(VALUE)) : 0; - int offset = document.getLineStartOffset(line) + start; - addChildWithName(placeholder, OFFSET, offset); - addChildWithName(placeholder, "useLength", "false"); - } - } - } - } - XmlSerializer.deserializeInto(this, state); - } catch (StudyUnrecognizedFormatException e) { - LOG.error(e); - } - } - - public static CCProjectService getInstance(@NotNull Project project) { - return ServiceManager.getService(project, CCProjectService.class); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java deleted file mode 100644 index a917b185b31c..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2000-2014 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.coursecreator; - -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.refactoring.listeners.RefactoringElementAdapter; -import com.intellij.refactoring.listeners.RefactoringElementListener; -import com.intellij.refactoring.listeners.RefactoringElementListenerProvider; -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.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - -public class CCRefactoringElementListenerProvider implements RefactoringElementListenerProvider { - - @Nullable - @Override - public RefactoringElementListener getListener(PsiElement element) { - return new CCRenameListener(element); - } - - - static class CCRenameListener extends RefactoringElementAdapter { - - private String myElementRelativePath; - - public CCRenameListener(PsiElement element) { - if (element instanceof PsiFile) { - PsiFile psiFile = (PsiFile)element; - myElementRelativePath = StudyUtils.pathRelativeToTask(psiFile.getVirtualFile()); - } - } - - @Override - protected void elementRenamedOrMoved(@NotNull PsiElement newElement) { - if (newElement instanceof PsiFile && myElementRelativePath != null) { - PsiFile psiFile = (PsiFile)newElement; - tryToRenameTaskFile(psiFile, myElementRelativePath); - } - } - - private static void tryToRenameTaskFile(PsiFile file, String oldName) { - final PsiDirectory taskDir = file.getContainingDirectory(); - final Project project = file.getProject(); - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - if (taskDir == null || !taskDir.getName().contains(EduNames.TASK)) { - return; - } - PsiDirectory lessonDir = taskDir.getParent(); - if (lessonDir == null || !lessonDir.getName().contains(EduNames.LESSON)) { - return; - } - Lesson lesson = course.getLesson(lessonDir.getName()); - if (lesson == null) { - return; - } - Task task = lesson.getTask(taskDir.getName()); - if (task == null) { - return; - } - Map taskFiles = task.getTaskFiles(); - TaskFile taskFile = task.getTaskFile(oldName); - if (taskFile == null) { - return; - } - taskFiles.remove(oldName); - taskFiles.put(StudyUtils.pathRelativeToTask(file.getVirtualFile()), taskFile); - } - - @Override - public void undoElementMovedOrRenamed(@NotNull PsiElement newElement, @NotNull String oldQualifiedName) { - - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCSubtaskEditorNotificationProvider.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCSubtaskEditorNotificationProvider.java deleted file mode 100644 index 716580bd0d72..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCSubtaskEditorNotificationProvider.java +++ /dev/null @@ -1,276 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.icons.AllIcons; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.colors.EditorColors; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.ui.popup.ListPopup; -import com.intellij.openapi.ui.popup.ListSeparator; -import com.intellij.openapi.ui.popup.PopupStep; -import com.intellij.openapi.ui.popup.util.BaseListPopupStep; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.EditorNotificationPanel; -import com.intellij.ui.EditorNotifications; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.EmptyIcon; -import com.jetbrains.edu.coursecreator.actions.CCNewSubtaskAction; -import com.jetbrains.edu.learning.StudySubtaskUtils; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.io.IOException; -import java.util.*; - -import static com.jetbrains.edu.coursecreator.CCUtils.renameFiles; - -public class CCSubtaskEditorNotificationProvider extends EditorNotifications.Provider implements DumbAware { - private static final Key KEY = Key.create("edu.coursecreator.subtask"); - public static final String SWITCH_SUBTASK = "Switch subtask"; - public static final Integer ADD_SUBTASK_ID = -1; - private static final Logger LOG = Logger.getInstance(CCSubtaskEditorNotificationProvider.class); - private final Project myProject; - - public CCSubtaskEditorNotificationProvider(Project project) { - myProject = project; - } - - @NotNull - @Override - public Key getKey() { - return KEY; - } - - @Nullable - @Override - public EditorNotificationPanel createNotificationPanel(@NotNull VirtualFile file, @NotNull FileEditor fileEditor) { - if (!CCUtils.isCourseCreator(myProject)) { - return null; - } - boolean isTestFile = CCUtils.isTestsFile(myProject, file); - if (!isTestFile && StudyUtils.getTaskFile(myProject, file) == null) { - return null; - } - Task task = StudyUtils.getTaskForFile(myProject, file); - if (task instanceof TaskWithSubtasks) { - final TaskWithSubtasks withSubtasks = (TaskWithSubtasks)task; - EditorNotificationPanel panel = new EditorNotificationPanel(EditorColors.GUTTER_BACKGROUND); - String header = (isTestFile ? "test" : "task") + " file"; - int activeSubtaskIndex = withSubtasks.getActiveSubtaskIndex() + 1; - int subtaskSize = withSubtasks.getLastSubtaskIndex() + 1; - panel.setText("This is a " + header + " for " + EduNames.SUBTASK + " " + activeSubtaskIndex + "/" + subtaskSize); - panel - .createActionLabel(SWITCH_SUBTASK, () -> createPopup(withSubtasks, myProject).show(RelativePoint.getSouthEastOf(panel))); - return panel; - } - return null; - } - - @NotNull - public static ListPopup createPopup(@NotNull TaskWithSubtasks task, @NotNull Project project) { - ArrayList values = new ArrayList<>(); - for (int i = 0; i <= task.getLastSubtaskIndex(); i++) { - values.add(i); - } - values.add(ADD_SUBTASK_ID); - return JBPopupFactory.getInstance().createListPopup(new SwitchSubtaskPopupStep(SWITCH_SUBTASK, values, task, project)); - } - - public static class SwitchSubtaskPopupStep extends BaseListPopupStep { - private final TaskWithSubtasks myTask; - private final Project myProject; - - public SwitchSubtaskPopupStep(@Nullable String title, - List values, - @NotNull TaskWithSubtasks task, - @NotNull Project project) { - super(title, values); - myTask = task; - myProject = project; - } - - @NotNull - @Override - public String getTextFor(Integer value) { - if (value.equals(ADD_SUBTASK_ID)) { - return CCNewSubtaskAction.NEW_SUBTASK; - } - int subtaskNum = value + 1; - return " " + EduNames.SUBTASK + " " + subtaskNum; - } - - @Override - public Icon getIconFor(Integer value) { - return value == myTask.getActiveSubtaskIndex() ? AllIcons.Actions.Checked : EmptyIcon.create(AllIcons.Actions.Checked); - } - - @Override - public PopupStep onChosen(Integer selectedValue, boolean finalChoice) { - if (finalChoice) { - if (selectedValue.equals(ADD_SUBTASK_ID)) { - return doFinalStep(() -> CCNewSubtaskAction.addSubtask(myTask, myProject)); - } - StudySubtaskUtils.switchStep(myProject, myTask, selectedValue); - } - else { - if (hasSubstep(selectedValue)) { - return new ActionsPopupStep(myTask, selectedValue, myProject); - } - } - return super.onChosen(selectedValue, false); - } - - @Override - public boolean hasSubstep(Integer selectedValue) { - return !selectedValue.equals(ADD_SUBTASK_ID); - } - - @Override - public int getDefaultOptionIndex() { - return myTask.getActiveSubtaskIndex(); - } - - @Nullable - @Override - public ListSeparator getSeparatorAbove(Integer value) { - return value.equals(ADD_SUBTASK_ID) ? new ListSeparator() : null; - } - } - - private static class ActionsPopupStep extends BaseListPopupStep { - - public static final String SELECT = "Select"; - public static final String DELETE = "Delete"; - private final TaskWithSubtasks myTask; - private final int mySubtaskIndex; - private final Project myProject; - - public ActionsPopupStep(@NotNull TaskWithSubtasks task, int subtaskIndex, @NotNull Project project) { - super(null, Arrays.asList(SELECT, DELETE)); - myTask = task; - mySubtaskIndex = subtaskIndex; - myProject = project; - } - - @Override - public PopupStep onChosen(String selectedValue, boolean finalChoice) { - if (finalChoice) { - if (selectedValue.equals(SELECT)) { - StudySubtaskUtils.switchStep(myProject, myTask, mySubtaskIndex); - } - else { - return deleteSubtask(); - } - } - return super.onChosen(selectedValue, finalChoice); - } - - @Nullable - private PopupStep deleteSubtask() { - final int lastSubtaskIndex = myTask.getLastSubtaskIndex(); - for (TaskFile taskFile : myTask.getTaskFiles().values()) { - List emptyPlaceholders = new ArrayList<>(); - for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { - Map infos = placeholder.getSubtaskInfos(); - if (infos.containsKey(mySubtaskIndex)) { - infos.remove(mySubtaskIndex); - if (infos.isEmpty()) { - emptyPlaceholders.add(placeholder); - } - } - } - taskFile.getAnswerPlaceholders().removeAll(emptyPlaceholders); - } - VirtualFile taskDir = myTask.getTaskDir(myProject); - if (taskDir == null) { - return FINAL_CHOICE; - } - deleteSubtaskFiles(taskDir); - if (mySubtaskIndex != lastSubtaskIndex) { - renameFiles(taskDir, myProject, mySubtaskIndex); - updateInfoIndexes(); - } - myTask.setLastSubtaskIndex(lastSubtaskIndex - 1); - int activeSubtaskIndex = myTask.getActiveSubtaskIndex(); - if (mySubtaskIndex != 0 && activeSubtaskIndex == mySubtaskIndex) { - StudySubtaskUtils.switchStep(myProject, myTask, mySubtaskIndex - 1); - } - if (activeSubtaskIndex > mySubtaskIndex) { - myTask.setActiveSubtaskIndex(activeSubtaskIndex - 1); - } - StudySubtaskUtils.updateUI(myProject, myTask, true); - for (VirtualFile file : FileEditorManager.getInstance(myProject).getOpenFiles()) { - EditorNotifications.getInstance(myProject).updateNotifications(file); - } - if (lastSubtaskIndex == 1) { - convertToTask(); - } - return FINAL_CHOICE; - } - - private void convertToTask() { - final Lesson lesson = myTask.getLesson(); - final List list = lesson.getTaskList(); - final int i = list.indexOf(myTask); - final Task task = new PyCharmTask(); - task.copyTaskParameters(myTask); - for (TaskFile taskFile : task.getTaskFiles().values()) { - taskFile.setTask(task); - } - list.set(i, task); - renameFiles(task.getTaskDir(myProject), myProject, -2); - } - - private void updateInfoIndexes() { - for (TaskFile taskFile : myTask.getTaskFiles().values()) { - for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { - List filtered = ContainerUtil.filter(placeholder.getSubtaskInfos().keySet(), index -> index > mySubtaskIndex); - Map savedInfos = new HashMap<>(); - for (Integer index : filtered) { - savedInfos.put(index, placeholder.getSubtaskInfos().get(index)); - placeholder.getSubtaskInfos().remove(index); - } - for (Integer index : filtered) { - placeholder.getSubtaskInfos().put(index - 1, savedInfos.get(index)); - } - } - } - } - - private void deleteSubtaskFiles(VirtualFile taskDir) { - ApplicationManager.getApplication().runWriteAction(() -> { - List filesToDelete = new ArrayList<>(); - for (VirtualFile file : taskDir.getChildren()) { - int index = CCUtils.getSubtaskIndex(myProject, file); - if (index != -1 && mySubtaskIndex == index) { - filesToDelete.add(file); - } - } - for (VirtualFile file : filesToDelete) { - try { - file.delete(myProject); - } - catch (IOException e) { - LOG.info(e); - } - } - }); - } - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCTestsTabTitleProvider.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCTestsTabTitleProvider.java deleted file mode 100644 index bcb645d56ede..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCTestsTabTitleProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import org.jetbrains.annotations.Nullable; - -public class CCTestsTabTitleProvider implements EditorTabTitleProvider { - @Nullable - @Override - public String getEditorTabTitle(Project project, VirtualFile file) { - if (!CCUtils.isCourseCreator(project)) { - return null; - } - if (!CCUtils.isTestsFile(project, file)) { - return null; - } - Course course = StudyTaskManager.getInstance(project).getCourse(); - assert course != null; - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator == null) { - return null; - } - return configurator.getTestFileName(); - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCUtils.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCUtils.java deleted file mode 100644 index 3f426eeda7c4..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCUtils.java +++ /dev/null @@ -1,310 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.google.common.collect.Collections2; -import com.intellij.lang.Language; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModuleRootModificationUtil; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileVisitor; -import com.intellij.psi.PsiDirectory; -import com.intellij.util.Function; -import com.intellij.util.containers.hash.HashMap; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -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.Arrays; -import java.util.Collections; -import java.util.Map; - -public class CCUtils { - public static final String ANSWER_EXTENSION_DOTTED = ".answer."; - public static final String TASK_DESCRIPTION_TEXT = "Write task description here using markdown or html"; - private static final Logger LOG = Logger.getInstance(CCUtils.class); - public static final String GENERATED_FILES_FOLDER = ".coursecreator"; - public static final String COURSE_MODE = "Course Creator"; - - public static int getSubtaskIndex(@NotNull Project project, @NotNull VirtualFile file) { - String fileName = file.getName(); - String name = FileUtil.getNameWithoutExtension(fileName); - if (!isTestsFile(project, file)) { - return -1; - } - if (!name.contains(EduNames.SUBTASK_MARKER)) { - return 0; - } - int markerIndex = name.indexOf(EduNames.SUBTASK_MARKER); - String index = name.substring(markerIndex + EduNames.SUBTASK_MARKER.length()); - if (index.isEmpty()) { - return -1; - } - try { - return Integer.valueOf(index); - } - catch (NumberFormatException e) { - return -1; - } - } - - /** - * This method decreases index and updates directory names of - * all tasks/lessons that have higher index than specified object - * - * @param dirs directories that are used to get tasks/lessons - * @param getStudyItem function that is used to get task/lesson from VirtualFile. This function can return null - * @param threshold index is used as threshold - * @param prefix task or lesson directory name prefix - */ - public static void updateHigherElements(VirtualFile[] dirs, - @NotNull final Function getStudyItem, - final int threshold, - final String prefix, - final int delta) { - ArrayList dirsToRename = new ArrayList<> - (Collections2.filter(Arrays.asList(dirs), dir -> { - final StudyItem item = getStudyItem.fun(dir); - if (item == null) { - return false; - } - int index = item.getIndex(); - return index > threshold; - })); - Collections.sort(dirsToRename, (o1, o2) -> { - StudyItem item1 = getStudyItem.fun(o1); - StudyItem item2 = getStudyItem.fun(o2); - //if we delete some dir we should start increasing numbers in dir names from the end - return (-delta) * EduUtils.INDEX_COMPARATOR.compare(item1, item2); - }); - - for (final VirtualFile dir : dirsToRename) { - final StudyItem item = getStudyItem.fun(dir); - final int newIndex = item.getIndex() + delta; - item.setIndex(newIndex); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - dir.rename(this, prefix + newIndex); - } - catch (IOException e) { - LOG.error(e); - } - } - }); - } - } - - public static boolean isLessonDir(PsiDirectory sourceDirectory) { - if (sourceDirectory == null) { - return false; - } - Project project = sourceDirectory.getProject(); - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course != null && isCourseCreator(project) && course.getLesson(sourceDirectory.getName()) != null) { - return true; - } - return false; - } - - - public static VirtualFile getGeneratedFilesFolder(@NotNull Project project, @NotNull Module module) { - VirtualFile baseDir = project.getBaseDir(); - VirtualFile folder = baseDir.findChild(GENERATED_FILES_FOLDER); - if (folder != null) { - return folder; - } - final Ref generatedRoot = new Ref<>(); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - generatedRoot.set(baseDir.createChildDirectory(this, GENERATED_FILES_FOLDER)); - VirtualFile contentRootForFile = - ProjectRootManager.getInstance(module.getProject()).getFileIndex().getContentRootForFile(generatedRoot.get()); - if (contentRootForFile == null) { - return; - } - ModuleRootModificationUtil.updateExcludedFolders(module, contentRootForFile, Collections.emptyList(), - Collections.singletonList(generatedRoot.get().getUrl())); - } - catch (IOException e) { - LOG.info("Failed to create folder for generated files", e); - } - } - }); - return generatedRoot.get(); - } - - @Nullable - public static VirtualFile generateFolder(@NotNull Project project, @NotNull Module module, String name) { - VirtualFile generatedRoot = getGeneratedFilesFolder(project, module); - if (generatedRoot == null) { - return null; - } - - final Ref folder = new Ref<>(generatedRoot.findChild(name)); - //need to delete old folder - ApplicationManager.getApplication().runWriteAction(() -> { - try { - if (folder.get() != null) { - folder.get().delete(null); - } - folder.set(generatedRoot.createChildDirectory(null, name)); - } - catch (IOException e) { - LOG.info("Failed to generate folder " + name, e); - } - }); - return folder.get(); - } - - public static boolean isCourseCreator(@NotNull Project project) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return false; - } - - return COURSE_MODE.equals(course.getCourseMode()); - } - - public static boolean isTestsFile(@NotNull Project project, @NotNull VirtualFile file) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return false; - } - Language language = course.getLanguageById(); - if (language == null) { - return false; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); - if (configurator == null) { - return false; - } - return configurator.isTestFile(file); - } - - public static void updateActionGroup(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - Project project = e.getProject(); - presentation.setEnabledAndVisible(project != null && isCourseCreator(project)); - } - - /** - * @param fromIndex -1 if task converted to TaskWithSubtasks, -2 if task converted from TaskWithSubtasks - */ - public static void renameFiles(VirtualFile taskDir, Project project, int fromIndex) { - ApplicationManager.getApplication().runWriteAction(() -> { - Map newNames = new HashMap<>(); - for (VirtualFile virtualFile : taskDir.getChildren()) { - int subtaskIndex = getSubtaskIndex(project, virtualFile); - if (subtaskIndex == -1) { - continue; - } - if (subtaskIndex > fromIndex) { - String index; - if (fromIndex == -1) { // add new subtask - index = "0"; - } - else { // remove subtask - index = fromIndex == -2 ? "" : Integer.toString(subtaskIndex - 1); - } - String fileName = virtualFile.getName(); - String nameWithoutExtension = FileUtil.getNameWithoutExtension(fileName); - String extension = FileUtilRt.getExtension(fileName); - int subtaskMarkerIndex = nameWithoutExtension.indexOf(EduNames.SUBTASK_MARKER); - String newName = subtaskMarkerIndex == -1 - ? nameWithoutExtension - : nameWithoutExtension.substring(0, subtaskMarkerIndex); - newName += index.isEmpty() ? "" : EduNames.SUBTASK_MARKER; - newName += index + "." + extension; - newNames.put(virtualFile, newName); - } - } - for (Map.Entry entry : newNames.entrySet()) { - try { - entry.getKey().rename(project, entry.getValue()); - } - catch (IOException e) { - LOG.info(e); - } - } - }); - } - - @Nullable - public static Lesson createAdditionalLesson(Course course, Project project) { - final VirtualFile baseDir = project.getBaseDir(); - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - - final Lesson lesson = new Lesson(); - lesson.setName(EduNames.PYCHARM_ADDITIONAL); - final Task task = new PyCharmTask(); - task.setLesson(lesson); - task.setName(EduNames.PYCHARM_ADDITIONAL); - task.setIndex(1); - - VfsUtilCore.visitChildrenRecursively(baseDir, new VirtualFileVisitor(VirtualFileVisitor.NO_FOLLOW_SYMLINKS) { - @Override - public boolean visitFile(@NotNull VirtualFile file) { - final String name = file.getName(); - if (name.equals(EduNames.COURSE_META_FILE) || name.equals(EduNames.HINTS) || name.startsWith(".")) return false; - String sanitizedName = FileUtil.sanitizeFileName(course.getName()); - final String archiveName = sanitizedName.startsWith("_") ? EduNames.COURSE : sanitizedName; - if (name.equals(archiveName + ".zip")) return false; - if (GENERATED_FILES_FOLDER.equals(name) || Project.DIRECTORY_STORE_FOLDER.equals(name)) { - return false; - } - if (file.isDirectory()) return true; - - if (StudyUtils.isTestsFile(project, name)) return true; - - if (name.contains(".iml") || (configurator != null && configurator.excludeFromArchive(file.getPath()))) { - return false; - } - final TaskFile taskFile = StudyUtils.getTaskFile(project, file); - if (taskFile == null) { - final String path = VfsUtilCore.getRelativePath(file, baseDir); - try { - if (EduUtils.isImage(file.getName())) { - task.addTestsTexts(path, Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream()))); - } - else { - task.addTestsTexts(path, FileUtil.loadTextAndClose(file.getInputStream())); - } - } - catch (IOException e) { - LOG.error("Can't find file " + path); - } - } - return true; - } - }); - if (task.getTestsText().isEmpty()) return null; - lesson.addTask(task); - lesson.setIndex(course.getLessons().size()); - return lesson; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java deleted file mode 100644 index 11d78532fe80..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/CCVirtualFileListener.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileEvent; -import com.intellij.openapi.vfs.VirtualFileListener; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; - -public class CCVirtualFileListener implements VirtualFileListener { - private final Project myProject; - - public CCVirtualFileListener(Project project) { - myProject = project; - } - - @Override - public void fileCreated(@NotNull VirtualFileEvent event) { - VirtualFile createdFile = event.getFile(); - if (createdFile.isDirectory()) { - return; - } - if (createdFile.getPath().contains(CCUtils.GENERATED_FILES_FOLDER)) { - return; - } - if (myProject.getBasePath() !=null && !FileUtil.isAncestor(myProject.getBasePath(), createdFile.getPath(), true)) { - return; - } - Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course == null) { - return; - } - TaskFile taskFile = StudyUtils.getTaskFile(myProject, createdFile); - if (taskFile != null) { - return; - } - - String taskRelativePath = StudyUtils.pathRelativeToTask(createdFile); - - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator != null && configurator.excludeFromArchive(createdFile.getPath())) { - return; - } - - if (CCUtils.isTestsFile(myProject, createdFile) - || taskRelativePath.contains(EduNames.WINDOW_POSTFIX) - || taskRelativePath.contains(EduNames.WINDOWS_POSTFIX) - || taskRelativePath.contains(EduNames.ANSWERS_POSTFIX)) { - return; - } - VirtualFile taskVF = StudyUtils.getTaskDir(createdFile); - if (taskVF == null) { - return; - } - Task task = StudyUtils.getTask(myProject, taskVF); - if (task == null) { - return; - } - - task.addTaskFile(taskRelativePath, task.getTaskFiles().size()+1); - } - - @Override - public void fileDeleted(@NotNull VirtualFileEvent event) { - VirtualFile removedFile = event.getFile(); - String path = removedFile.getPath(); - if (path.contains(CCUtils.GENERATED_FILES_FOLDER)) { - return; - } - - if (myProject == null) { - return; - } - if (myProject.getBasePath() !=null && !FileUtil.isAncestor(myProject.getBasePath(), removedFile.getPath(), true)) { - return; - } - Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course == null) { - return; - } - final TaskFile taskFile = StudyUtils.getTaskFile(myProject, removedFile); - if (taskFile != null) { - deleteTaskFile(removedFile, taskFile); - return; - } - if (removedFile.getName().contains(EduNames.TASK)) { - deleteTask(course, removedFile); - } - if (removedFile.getName().contains(EduNames.LESSON)) { - deleteLesson(course, removedFile, myProject); - } - } - - private static void deleteLesson(@NotNull final Course course, @NotNull final VirtualFile removedLessonFile, Project project) { - Lesson removedLesson = course.getLesson(removedLessonFile.getName()); - if (removedLesson == null) { - return; - } - VirtualFile courseDir = project.getBaseDir(); - CCUtils.updateHigherElements(courseDir.getChildren(), file -> course.getLesson(file.getName()), removedLesson.getIndex(), EduNames.LESSON, -1); - course.removeLesson(removedLesson); - } - - private static void deleteTask(@NotNull final Course course, @NotNull final VirtualFile removedTask) { - VirtualFile lessonDir = removedTask.getParent(); - if (lessonDir == null || !lessonDir.getName().contains(EduNames.LESSON)) { - return; - } - final Lesson lesson = course.getLesson(lessonDir.getName()); - if (lesson == null) { - return; - } - Task task = lesson.getTask(removedTask.getName()); - if (task == null) { - return; - } - CCUtils.updateHigherElements(lessonDir.getChildren(), file -> lesson.getTask(file.getName()), task.getIndex(), EduNames.TASK, -1); - lesson.getTaskList().remove(task); - } - - private static void deleteTaskFile(@NotNull final VirtualFile removedTaskFile, TaskFile taskFile) { - Task task = taskFile.getTask(); - if (task == null) { - return; - } - task.getTaskFiles().remove(StudyUtils.pathRelativeToTask(removedTaskFile)); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCAddAsTaskFile.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCAddAsTaskFile.java deleted file mode 100644 index 9b9904b0f883..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCAddAsTaskFile.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.command.undo.BasicUndoableAction; -import com.intellij.openapi.command.undo.UnexpectedUndoException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; - -public class CCAddAsTaskFile extends CCTaskFileActionBase { - public static final String ACTION_NAME = "Make Visible to Student"; - - public CCAddAsTaskFile() { - super(ACTION_NAME); - } - - - protected void performAction(VirtualFile file, Task task, Course course, Project project) { - EduUtils.runUndoableAction(project, ACTION_NAME, new AddTaskFile(file, null, project, task)); - } - - protected boolean isAvailable(Project project, VirtualFile file) { - return StudyUtils.getTaskFile(project, file) == null && !CCUtils.isTestsFile(project, file); - } - - private static class AddTaskFile extends BasicUndoableAction { - private final VirtualFile myFile; - private TaskFile myTaskFile; - private final Project myProject; - private final Task myTask; - - public AddTaskFile(VirtualFile file, TaskFile taskFile, Project project, Task task) { - super(file); - myFile = file; - myTaskFile = taskFile; - myProject = project; - myTask = task; - } - - @Override - public void undo() throws UnexpectedUndoException { - if (myTaskFile == null) return; - CCHideFromStudent.hideFromStudent(myFile, myProject, myTask.getTaskFiles(), myTaskFile); - ProjectView.getInstance(myProject).refresh(); - } - - @Override - public void redo() throws UnexpectedUndoException { - if (myTaskFile != null) { - myTask.addTaskFile(myTaskFile); - } else { - final String taskRelativePath = StudyUtils.pathRelativeToTask(myFile); - myTask.addTaskFile(taskRelativePath, myTask.getTaskFiles().size()); - myTaskFile = myTask.getTaskFile(taskRelativePath); - } - ProjectView.getInstance(myProject).refresh(); - } - - @Override - public boolean isGlobal() { - return true; - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCChangeCourseInfo.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCChangeCourseInfo.java deleted file mode 100644 index 4421e17185b2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCChangeCourseInfo.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.ide.IdeView; -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogBuilder; -import com.intellij.profile.codeInspection.ProjectInspectionProfileManager; -import com.intellij.psi.PsiDirectory; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.ui.CCNewProjectPanel; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; - -public class CCChangeCourseInfo extends DumbAwareAction { - private static final String ACTION_TEXT = "Change Course Information"; - - public CCChangeCourseInfo() { - super(ACTION_TEXT, ACTION_TEXT, null); - } - - @Override - public void update(@NotNull AnActionEvent event) { - final Project project = event.getProject(); - final Presentation presentation = event.getPresentation(); - if (project == null) { - return; - } - presentation.setEnabledAndVisible(false); - if (!CCUtils.isCourseCreator(project)) { - return; - } - final IdeView view = event.getData(LangDataKeys.IDE_VIEW); - if (view == null) { - return; - } - final PsiDirectory[] directories = view.getDirectories(); - if (directories.length == 0) { - return; - } - presentation.setEnabledAndVisible(true); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - Project project = e.getProject(); - if (project == null) { - return; - } - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - - CCNewProjectPanel panel = - new CCNewProjectPanel(course.getName(), Course.getAuthorsString(course.getAuthors()), course.getDescription()); - setupLanguageLevels(course, panel); - DialogBuilder builder = createChangeInfoDialog(project, panel); - if (builder.showAndGet()) { - course.setAuthorsAsString(panel.getAuthors()); - course.setName(panel.getName()); - course.setDescription(panel.getDescription()); - setVersion(course, panel); - ProjectView.getInstance(project).refresh(); - ProjectInspectionProfileManager.getInstance(project).fireProfileChanged(); - } - } - - protected void setVersion(Course course, CCNewProjectPanel panel) {} - - protected void setupLanguageLevels(Course course, CCNewProjectPanel panel) {} - - private static DialogBuilder createChangeInfoDialog(Project project, @NotNull CCNewProjectPanel panel) { - DialogBuilder builder = new DialogBuilder(project); - - builder.setTitle(ACTION_TEXT); - JPanel changeInfoPanel = panel.getMainPanel(); - changeInfoPanel.setPreferredSize(new Dimension(450, 300)); - changeInfoPanel.setMinimumSize(new Dimension(450, 300)); - builder.setCenterPanel(changeInfoPanel); - - return builder; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java deleted file mode 100644 index 1ba3139ef00b..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateCourseArchive.java +++ /dev/null @@ -1,222 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.util.containers.hash.HashMap; -import com.intellij.util.io.ZipUtil; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.ui.CreateCourseArchiveDialog; -import com.jetbrains.edu.learning.StudySerializationUtils; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import org.jetbrains.annotations.NotNull; - -import java.io.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.zip.ZipOutputStream; - -public class CCCreateCourseArchive extends DumbAwareAction { - private static final Logger LOG = Logger.getInstance(CCCreateCourseArchive.class.getName()); - public static final String GENERATE_COURSE_ARCHIVE = "Generate Course Archive"; - private String myZipName; - private String myLocationDir; - - public void setZipName(String zipName) { - myZipName = zipName; - } - - public void setLocationDir(String locationDir) { - myLocationDir = locationDir; - } - - public CCCreateCourseArchive() { - super(GENERATE_COURSE_ARCHIVE, GENERATE_COURSE_ARCHIVE, null); - } - - @Override - public void update(@NotNull AnActionEvent e) { - Presentation presentation = e.getPresentation(); - Project project = e.getProject(); - presentation.setEnabledAndVisible(project != null && CCUtils.isCourseCreator(project)); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final Project project = e.getData(CommonDataKeys.PROJECT); - final Module module = e.getData(LangDataKeys.MODULE); - if (project == null || module == null) { - return; - } - CreateCourseArchiveDialog dlg = new CreateCourseArchiveDialog(project, this); - dlg.show(); - if (dlg.getExitCode() != DialogWrapper.OK_EXIT_CODE) { - return; - } - createCourseArchive(project, module, myZipName, myLocationDir, true); - EduUsagesCollector.createdCourseArchive(); - } - - public static void createCourseArchive(final Project project, Module module, String zipName, String locationDir, boolean showMessage) { - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) return; - final VirtualFile baseDir = project.getBaseDir(); - VirtualFile archiveFolder = CCUtils.generateFolder(project, module, zipName); - if (archiveFolder == null) { - return; - } - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - archiveFolder.refresh(false, true); - Course courseCopy = course.copy(); - replaceAnswerFilesWithTaskFiles(courseCopy); - courseCopy.sortLessons(); - createAdditionalFiles(courseCopy); - generateJson(archiveFolder, courseCopy); - VirtualFileManager.getInstance().refreshWithoutFileWatcher(false); - packCourse(archiveFolder, locationDir, zipName, showMessage); - synchronize(project); - } - - private void createAdditionalFiles(Course course) { - final Lesson lesson = CCUtils.createAdditionalLesson(course, project); - if (lesson != null) { - course.addLesson(lesson); - } - } - - private void replaceAnswerFilesWithTaskFiles(Course courseCopy) { - for (Lesson lesson : courseCopy.getLessons()) { - String lessonDirName = EduNames.LESSON + String.valueOf(lesson.getIndex()); - final VirtualFile lessonDir = baseDir.findChild(lessonDirName); - if (lessonDir == null) continue; - for (Task task : lesson.getTaskList()) { - final VirtualFile taskDir = task.getTaskDir(project); - if (taskDir == null) continue; - convertToStudentTaskFiles(task, taskDir); - addTestsToTask(task); - } - } - } - - private void convertToStudentTaskFiles(Task task, VirtualFile taskDir) { - final HashMap studentTaskFiles = new HashMap<>(); - for (Map.Entry entry : task.getTaskFiles().entrySet()) { - VirtualFile answerFile = taskDir.findFileByRelativePath(entry.getKey()); - if (answerFile == null) { - continue; - } - final TaskFile studentFile = EduUtils.createStudentFile(project, answerFile, task, 0); - if (studentFile != null) { - studentTaskFiles.put(entry.getKey(), studentFile); - } - } - task.taskFiles = studentTaskFiles; - } - - private void addTestsToTask(Task task) { - final List testFiles = getTestFiles(task, project); - for (VirtualFile file : testFiles) { - try { - task.addTestsTexts(file.getName(), VfsUtilCore.loadText(file)); - } - catch (IOException e) { - LOG.warn("Failed to load text " + file.getName()); - } - } - } - - private List getTestFiles(@NotNull Task task, @NotNull Project project) { - List testFiles = new ArrayList<>(); - VirtualFile taskDir = task.getTaskDir(project); - if (taskDir == null) { - return testFiles; - } - testFiles.addAll(Arrays.stream(taskDir.getChildren()) - .filter(file -> StudyUtils.isTestsFile(project, file.getName())) - .collect(Collectors.toList())); - return testFiles; - } - }); - } - - private static void synchronize(@NotNull final Project project) { - VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); - ProjectView.getInstance(project).refresh(); - } - - private static void packCourse(@NotNull final VirtualFile baseDir, String locationDir, String zipName, boolean showMessage) { - try { - final File zipFile = new File(locationDir, zipName + ".zip"); - ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile))); - VirtualFile[] courseFiles = baseDir.getChildren(); - for (VirtualFile file : courseFiles) { - ZipUtil.addFileOrDirRecursively(zos, null, new File(file.getPath()), file.getName(), null, null); - } - zos.close(); - if (showMessage) { - ApplicationManager.getApplication().invokeLater( - () -> Messages.showInfoMessage("Course archive was saved to " + zipFile.getPath(), - "Course Archive Was Created Successfully")); - - } - } - catch (IOException e1) { - LOG.error(e1); - } - } - - @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - private static void generateJson(VirtualFile parentDir, Course course) { - final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation(). - registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskAdapter()).create(); - final String json = gson.toJson(course); - final File courseJson = new File(parentDir.getPath(), EduNames.COURSE_META_FILE); - OutputStreamWriter outputStreamWriter = null; - try { - outputStreamWriter = new OutputStreamWriter(new FileOutputStream(courseJson), "UTF-8"); - outputStreamWriter.write(json); - } - catch (Exception e) { - Messages.showErrorDialog(e.getMessage(), "Failed to Generate Json"); - LOG.info(e); - } - finally { - try { - if (outputStreamWriter != null) { - outputStreamWriter.close(); - } - } - catch (IOException e1) { - //close silently - } - } - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateLesson.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateLesson.java deleted file mode 100644 index 09e24daf3360..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateLesson.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.ide.IdeView; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiManager; -import com.intellij.util.Function; -import com.jetbrains.edu.learning.EduPluginConfigurator; -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.StudyItem; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class CCCreateLesson extends CCCreateStudyItemActionBase { - public static final String TITLE = "Create New " + EduNames.LESSON_TITLED; - - public CCCreateLesson() { - super(EduNames.LESSON_TITLED, TITLE, EducationalCoreIcons.Lesson); - } - - @Nullable - @Override - protected PsiDirectory getParentDir(@NotNull Project project, @NotNull Course course, @NotNull PsiDirectory directory) { - return PsiManager.getInstance(project).findDirectory(project.getBaseDir()); - } - - @Override - protected void addItem(@NotNull Course course, @NotNull StudyItem item) { - course.addLesson(((Lesson)item)); - } - - @Override - protected Function getStudyOrderable(@NotNull final StudyItem item) { - return (Function)file -> { - if (item instanceof Lesson) { - return ((Lesson)item).getCourse().getLesson(file.getName()); - } - return null; - }; - } - - @Override - @Nullable - protected PsiDirectory createItemDir(@NotNull final Project project, @NotNull final StudyItem item, - @Nullable final IdeView view, @NotNull final PsiDirectory parentDirectory, - @NotNull final Course course) { - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator == null) { - return null; - } - return configurator.createLessonContent(project, (Lesson)item, view, parentDirectory); - } - - @Override - protected int getSiblingsSize(@NotNull Course course, @Nullable StudyItem item) { - return course.getLessons().size(); - } - - @Nullable - @Override - protected StudyItem getParentItem(@NotNull Course course, @NotNull PsiDirectory directory) { - return null; - } - - @Nullable - @Override - protected StudyItem getThresholdItem(@NotNull final Course course, @NotNull final PsiDirectory sourceDirectory) { - return course.getLesson(sourceDirectory.getName()); - } - - @Override - protected boolean isAddedAsLast(@NotNull PsiDirectory sourceDirectory, - @NotNull Project project, - @NotNull Course course) { - return sourceDirectory.getVirtualFile().equals(project.getBaseDir()); - } - - @Override - protected void sortSiblings(@NotNull Course course, @Nullable StudyItem parentItem) { - course.sortLessons(); - } - - @Override - protected String getItemName() { - return EduNames.LESSON; - } - - @Override - protected StudyItem createAndInitItem(@NotNull Course course, @Nullable StudyItem parentItem, String name, int index) { - Lesson lesson = new Lesson(); - lesson.setName(name); - lesson.setCourse(course); - lesson.setIndex(index); - return lesson; - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateStudyItemActionBase.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateStudyItemActionBase.java deleted file mode 100644 index 900ec4a6b5dc..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateStudyItemActionBase.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.ide.IdeView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.util.Function; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.ui.CCCreateStudyItemDialog; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public abstract class CCCreateStudyItemActionBase extends DumbAwareAction { - - public CCCreateStudyItemActionBase(String text, String description, Icon icon) { - super(text, description, icon); - } - - @Override - public void actionPerformed(AnActionEvent e) { - final IdeView view = e.getData(LangDataKeys.IDE_VIEW); - final Project project = e.getProject(); - if (view == null || project == null) { - return; - } - final PsiDirectory[] directories = view.getDirectories(); - if (directories.length == 0 || directories.length > 1) { - return; - } - - final PsiDirectory directory = directories[0]; - if (directory == null) return; - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - createItem(view, project, directory, course); - } - - @Override - public void update(@NotNull AnActionEvent event) { - final Presentation presentation = event.getPresentation(); - presentation.setEnabledAndVisible(false); - final Project project = event.getData(CommonDataKeys.PROJECT); - final IdeView view = event.getData(LangDataKeys.IDE_VIEW); - if (project == null || view == null) { - return; - } - if (!StudyUtils.isStudyProject(project) || !CCUtils.isCourseCreator(project)) { - return; - } - final PsiDirectory[] directories = view.getDirectories(); - if (directories.length == 0 || directories.length > 1) { - return; - } - - final PsiDirectory sourceDirectory = directories[0]; - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null || sourceDirectory == null) { - return; - } - if (!isAddedAsLast(sourceDirectory, project, course) && getThresholdItem(course, sourceDirectory) == null) { - return; - } - if (CommonDataKeys.PSI_FILE.getData(event.getDataContext()) != null) { - return; - } - presentation.setEnabledAndVisible(true); - } - - @Nullable - protected abstract PsiDirectory getParentDir(@NotNull final Project project, - @NotNull final Course course, - @NotNull final PsiDirectory directory); - - @Nullable - public PsiDirectory createItem(@Nullable final IdeView view, @NotNull final Project project, - @NotNull final PsiDirectory sourceDirectory, @NotNull final Course course) { - StudyItem parentItem = getParentItem(course, sourceDirectory); - final StudyItem item = getItem(sourceDirectory, project, course, view, parentItem); - if (item == null) { - return null; - } - final PsiDirectory parentDir = getParentDir(project, course, sourceDirectory); - if (parentDir == null) { - return null; - } - CCUtils.updateHigherElements(parentDir.getVirtualFile().getChildren(), getStudyOrderable(item), - item.getIndex() - 1, getItemName(), 1); - addItem(course, item); - sortSiblings(course, parentItem); - return createItemDir(project, item, view, parentDir, course); - } - - protected abstract void addItem(@NotNull final Course course, @NotNull final StudyItem item); - - protected abstract Function getStudyOrderable(@NotNull final StudyItem item); - - protected abstract PsiDirectory createItemDir(@NotNull final Project project, @NotNull final StudyItem item, - @Nullable final IdeView view, @NotNull final PsiDirectory parentDirectory, - @NotNull final Course course); - - @Nullable - protected StudyItem getItem(@NotNull final PsiDirectory sourceDirectory, - @NotNull final Project project, - @NotNull final Course course, - @Nullable IdeView view, - @Nullable StudyItem parentItem) { - - String itemName; - int itemIndex; - if (isAddedAsLast(sourceDirectory, project, course)) { - itemIndex = getSiblingsSize(course, parentItem) + 1; - String suggestedName = getItemName() + itemIndex; - itemName = view == null ? suggestedName : Messages.showInputDialog("Name:", getTitle(), - null, suggestedName, null); - } - else { - StudyItem thresholdItem = getThresholdItem(course, sourceDirectory); - if (thresholdItem == null) { - return null; - } - final int index = thresholdItem.getIndex(); - CCCreateStudyItemDialog dialog = new CCCreateStudyItemDialog(project, getItemName(), thresholdItem.getName(), index); - dialog.show(); - if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) { - return null; - } - itemName = dialog.getName(); - itemIndex = index + dialog.getIndexDelta(); - } - if (itemName == null) { - return null; - } - return createAndInitItem(course, parentItem, itemName, itemIndex); - } - - protected abstract int getSiblingsSize(@NotNull final Course course, @Nullable final StudyItem parentItem); - - @NotNull - protected String getTitle() { - return "Create New " + StringUtil.toTitleCase(getItemName()); - } - - @Nullable - protected abstract StudyItem getParentItem(@NotNull final Course course, @NotNull final PsiDirectory directory); - - @Nullable - protected abstract StudyItem getThresholdItem(@NotNull final Course course, @NotNull final PsiDirectory sourceDirectory); - - protected abstract boolean isAddedAsLast(@NotNull final PsiDirectory sourceDirectory, - @NotNull final Project project, - @NotNull final Course course); - - protected abstract void sortSiblings(@NotNull final Course course, @Nullable final StudyItem parentItem); - - protected abstract String getItemName(); - - protected abstract StudyItem createAndInitItem(@NotNull final Course course, - @Nullable final StudyItem parentItem, - String name, - int index); -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateTask.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateTask.java deleted file mode 100644 index 7c56613a357b..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCCreateTask.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.ide.IdeView; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.util.Function; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; - -public class CCCreateTask extends CCCreateStudyItemActionBase { - public static final String TITLE = "Create New " + EduNames.TASK_TITLED; - - public CCCreateTask() { - super(EduNames.TASK_TITLED, TITLE, EducationalCoreIcons.Task); - } - - @Nullable - @Override - protected PsiDirectory getParentDir(@NotNull Project project, @NotNull Course course, @NotNull PsiDirectory directory) { - if (isAddedAsLast(directory, project, course)) { - return directory; - } - return directory.getParent(); - } - - @Override - protected void addItem(@NotNull Course course, @NotNull StudyItem item) { - if (item instanceof Task) { - Task task = (Task)item; - task.getLesson().addTask(task); - } - } - - @Override - protected Function getStudyOrderable(@NotNull final StudyItem item) { - return (Function)file -> { - if (item instanceof Task) { - return ((Task)item).getLesson().getTask(file.getName()); - } - return null; - }; - } - - @Override - @Nullable - protected PsiDirectory createItemDir(@NotNull final Project project, @NotNull final StudyItem item, - @Nullable final IdeView view, @NotNull final PsiDirectory parentDirectory, - @NotNull final Course course) { - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator != null) { - return configurator.createTaskContent(project, (Task)item, view, parentDirectory, course); - } - return null; - } - - @Override - protected int getSiblingsSize(@NotNull Course course, @Nullable StudyItem parentItem) { - if (parentItem instanceof Lesson) { - return ((Lesson)parentItem).getTaskList().size(); - } - return 0; - } - - @Nullable - @Override - protected StudyItem getParentItem(@NotNull Course course, @NotNull PsiDirectory directory) { - Task task = (Task)getThresholdItem(course, directory); - if (task == null) { - return course.getLesson(directory.getName()); - } - return task.getLesson(); - } - - @Nullable - @Override - protected StudyItem getThresholdItem(@NotNull Course course, @NotNull PsiDirectory sourceDirectory) { - return EduUtils.getTask(sourceDirectory, course); - } - - @Override - protected boolean isAddedAsLast(@NotNull PsiDirectory sourceDirectory, - @NotNull Project project, - @NotNull Course course) { - return course.getLesson(sourceDirectory.getName()) != null; - } - - @Override - protected void sortSiblings(@NotNull Course course, @Nullable StudyItem parentItem) { - if (parentItem instanceof Lesson) { - Collections.sort(((Lesson)parentItem).getTaskList(), EduUtils.INDEX_COMPARATOR); - } - } - - @Override - protected String getItemName() { - return EduNames.TASK; - } - - @Override - protected StudyItem createAndInitItem(@NotNull Course course, @Nullable StudyItem parentItem, String name, int index) { - final Task task = new PyCharmTask(name); - task.setIndex(index); - if (parentItem == null) { - return null; - } - task.setLesson(((Lesson)parentItem)); - task.addTaskText(task.getTaskDescriptionName(), CCUtils.TASK_DESCRIPTION_TEXT); - return task; - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCEditHintAction.kt b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCEditHintAction.kt deleted file mode 100644 index d69c758a9bce..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCEditHintAction.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions - -import com.intellij.icons.AllIcons -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.util.text.StringUtil -import com.jetbrains.edu.coursecreator.CCUtils -import com.jetbrains.edu.coursecreator.actions.placeholder.CCCreateAnswerPlaceholderDialog -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder - -class CCEditHintAction(val myPlaceholder: AnswerPlaceholder?) : AnAction("Edit Hint", "Edit Hint", AllIcons.Modules.Edit) { - - override fun actionPerformed(e: AnActionEvent?) { - val dlg = CCCreateAnswerPlaceholderDialog(e!!.project!!, myPlaceholder!!.taskText, myPlaceholder.hints) - dlg.title = "Edit Answer Placeholder" - if (dlg.showAndGet()) { - val answerPlaceholderText = dlg.taskText - myPlaceholder.taskText = answerPlaceholderText - myPlaceholder.length = if (myPlaceholder.activeSubtaskInfo.isNeedInsertText) 0 - else StringUtil.notNullize(answerPlaceholderText).length - myPlaceholder.hints = dlg.hints - } - } - - override fun update(e: AnActionEvent) { - e.presentation.isEnabledAndVisible = CCUtils.isCourseCreator(e.project!!) && myPlaceholder != null - } -} - diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCEditTaskTextAction.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCEditTaskTextAction.java deleted file mode 100644 index e3c3862f27ea..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCEditTaskTextAction.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.icons.AllIcons; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.ToggleAction; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.ui.StudyToolWindow; -import org.jetbrains.annotations.NotNull; - -public class CCEditTaskTextAction extends ToggleAction implements DumbAware { - private static final Logger LOG = Logger.getInstance(CCEditTaskTextAction.class); - private static final String EDITING_MODE = "Editing Mode"; - - public CCEditTaskTextAction() { - super(EDITING_MODE, EDITING_MODE, AllIcons.Modules.Edit); - } - - @Override - public boolean isSelected(AnActionEvent e) { - Project project = e.getProject(); - if (project == null) { - return false; - } - return StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindow.StudyToolWindowMode.EDITING; - } - - @Override - public void setSelected(AnActionEvent e, boolean state) { - Project project = e.getProject(); - if (project == null) { - return; - } - StudyToolWindow window = StudyUtils.getStudyToolWindow(project); - if (window == null) { - return; - } - Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); - if (editor == null) { - return; - } - VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(editor.getDocument()); - if (virtualFile == null) { - StudyTaskManager.getInstance(project).setTurnEditingMode(true); - return; - } - - Task task = StudyUtils.getTaskForFile(project, virtualFile); - if (task == null) { - StudyTaskManager.getInstance(project).setTurnEditingMode(true); - return; - } - if (!state) { - window.leaveEditingMode(project); - return; - } - window.enterEditingMode(project); - } - - @Override - public void update(@NotNull AnActionEvent e) { - Project project = e.getProject(); - if (project == null) { - return; - } - - super.update(e); - if (!CCUtils.isCourseCreator(project)) { - e.getPresentation().setEnabledAndVisible(false); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFileActionGroup.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFileActionGroup.java deleted file mode 100644 index a163c1542d37..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFileActionGroup.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.project.DumbAware; -import com.jetbrains.edu.coursecreator.CCUtils; - -public class CCFileActionGroup extends DefaultActionGroup implements DumbAware{ - @Override - public void update(AnActionEvent e) { - CCUtils.updateActionGroup(e); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java deleted file mode 100644 index 381b6b99101e..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCFromCourseArchive.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.icons.AllIcons; -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileChooser.FileChooser; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.jetbrains.edu.coursecreator.CCUtils; -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.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; - -public class CCFromCourseArchive extends DumbAwareAction { - public CCFromCourseArchive() { - super("Unpack Course Archive", "Unpack Course Archive", AllIcons.FileTypes.Archive); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final Project project = e.getData(CommonDataKeys.PROJECT); - if (project == null) { - return; - } - unpackCourseArchive(project); - } - - private static void unpackCourseArchive(final Project project) { - FileChooserDescriptor descriptor = - new FileChooserDescriptor(true, true, true, true, true, false); - - final VirtualFile virtualFile = FileChooser.chooseFile(descriptor, project, null); - if (virtualFile == null) { - return; - } - final String basePath = project.getBasePath(); - if (basePath == null) return; - - Course course = StudyProjectGenerator.getCourse(virtualFile.getPath()); - if (course == null) { - Messages.showErrorDialog("This course is incompatible with current version", "Failed to Unpack Course"); - return; - } - generateFromStudentCourse(project, course); - } - - public static void generateFromStudentCourse(Project project, Course course) { - StudyTaskManager.getInstance(project).setCourse(course); - course.setCourseMode(CCUtils.COURSE_MODE); - final VirtualFile baseDir = project.getBaseDir(); - final Application application = ApplicationManager.getApplication(); - - application.invokeAndWait(() -> application.runWriteAction(() -> { - final VirtualFile[] children = baseDir.getChildren(); - for (VirtualFile child : children) { - StudyUtils.deleteFile(child); - } - StudyGenerator.createCourse(course, baseDir); - })); - baseDir.refresh(false, true); - - int index = 1; - int taskIndex = 1; - for (Lesson lesson : course.getLessons()) { - final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(index)); - lesson.setIndex(index); - if (lessonDir == null) continue; - for (Task task : lesson.getTaskList()) { - final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + String.valueOf(taskIndex)); - task.setIndex(taskIndex); - task.setLesson(lesson); - if (taskDir == null) continue; - for (final Map.Entry entry : task.getTaskFiles().entrySet()) { - application.invokeAndWait(() -> application.runWriteAction(() -> createAnswerFile(project, taskDir, entry))); - } - taskIndex += 1; - } - index += 1; - taskIndex = 1; - } - course.initCourse(true); - application.invokeAndWait(() -> StudyUtils.registerStudyToolWindow(course, project)); - synchronize(project); - } - - public static void createAnswerFile(@NotNull final Project project, - @NotNull final VirtualFile userFileDir, - @NotNull final Map.Entry taskFileEntry) { - final String name = taskFileEntry.getKey(); - final TaskFile taskFile = taskFileEntry.getValue(); - VirtualFile file = userFileDir.findFileByRelativePath(name); - assert file != null; - final Document document = FileDocumentManager.getInstance().getDocument(file); - if (document == null) return; - - CommandProcessor.getInstance().executeCommand(project,() -> ApplicationManager.getApplication().runWriteAction( - () -> document.replaceString(0, document.getTextLength(), document.getCharsSequence())), - "Create answer document", "Create answer document"); - EduDocumentListener listener = new EduDocumentListener(taskFile, false); - document.addDocumentListener(listener); - taskFile.sortAnswerPlaceholders(); - - for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) { - replaceAnswerPlaceholder(document, placeholder); - } - for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { - placeholder.setUseLength(false); - } - - CommandProcessor.getInstance().executeCommand(project, () -> ApplicationManager.getApplication().runWriteAction( - () -> FileDocumentManager.getInstance().saveDocument(document)),"Create answer document", "Create answer document"); - document.removeDocumentListener(listener); - } - - private static void replaceAnswerPlaceholder(@NotNull final Document document, - @NotNull final AnswerPlaceholder placeholder) { - final int offset = placeholder.getOffset(); - final String text = document.getText(TextRange.create(offset, offset + placeholder.getRealLength())); - placeholder.setTaskText(text); - placeholder.init(); - String replacementText = placeholder.getPossibleAnswer(); - - CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> { - document.replaceString(offset, offset + placeholder.getRealLength(), replacementText); - FileDocumentManager.getInstance().saveDocument(document); - })); - } - - private static void synchronize(@NotNull final Project project) { - VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); - ProjectView.getInstance(project).refresh(); - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java deleted file mode 100644 index accb05906ebf..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.command.undo.BasicUndoableAction; -import com.intellij.openapi.command.undo.UnexpectedUndoException; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; - -public class CCHideFromStudent extends CCTaskFileActionBase { - private static final String ACTION_NAME = "Hide from Student"; - - public CCHideFromStudent() { - super(ACTION_NAME); - } - - @Override - protected void performAction(VirtualFile file, Task task, Course course, Project project) { - TaskFile taskFile = StudyUtils.getTaskFile(project, file); - if (taskFile == null) { - return; - } - EduUtils.runUndoableAction(project, ACTION_NAME, new HideTaskFile(project, file, task, taskFile)); - } - - private static class HideTaskFile extends BasicUndoableAction { - - private final Project myProject; - private final VirtualFile myFile; - private final Task myTask; - private final TaskFile myTaskFile; - - public HideTaskFile(Project project, VirtualFile file, Task task, TaskFile taskFile) { - super(file); - myProject = project; - myFile = file; - myTask = task; - myTaskFile = taskFile; - } - - @Override - public void undo() throws UnexpectedUndoException { - myTask.getTaskFiles().put(StudyUtils.pathRelativeToTask(myFile), myTaskFile); - if (!myTaskFile.getAnswerPlaceholders().isEmpty() && FileEditorManager.getInstance(myProject).isFileOpen(myFile)) { - for (FileEditor fileEditor : FileEditorManager.getInstance(myProject).getEditors(myFile)) { - if (fileEditor instanceof TextEditor) { - Editor editor = ((TextEditor)fileEditor).getEditor(); - StudyUtils.drawAllAnswerPlaceholders(editor, myTaskFile); - } - } - } - ProjectView.getInstance(myProject).refresh(); - } - - @Override - public void redo() throws UnexpectedUndoException { - hideFromStudent(myFile, myProject, myTask.getTaskFiles(), myTaskFile); - ProjectView.getInstance(myProject).refresh(); - } - - @Override - public boolean isGlobal() { - return true; - } - } - - public static void hideFromStudent(VirtualFile file, Project project, Map taskFiles, @NotNull final TaskFile taskFile) { - if (!taskFile.getAnswerPlaceholders().isEmpty() && FileEditorManager.getInstance(project).isFileOpen(file)) { - for (FileEditor fileEditor : FileEditorManager.getInstance(project).getEditors(file)) { - if (fileEditor instanceof TextEditor) { - Editor editor = ((TextEditor)fileEditor).getEditor(); - editor.getMarkupModel().removeAllHighlighters(); - } - } - } - String taskRelativePath = StudyUtils.pathRelativeToTask(file); - taskFiles.remove(taskRelativePath); - } - - @Override - protected boolean isAvailable(Project project, VirtualFile file) { - return StudyUtils.getTaskFile(project, file) != null; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCNewSubtaskAction.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCNewSubtaskAction.java deleted file mode 100644 index e2a79296370f..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCNewSubtaskAction.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudySubtaskUtils; -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.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -import static com.jetbrains.edu.coursecreator.CCUtils.renameFiles; - - -public class CCNewSubtaskAction extends DumbAwareAction { - public static final String NEW_SUBTASK = "Add Subtask"; - - public CCNewSubtaskAction() { - super(NEW_SUBTASK); - } - - @Override - public void actionPerformed(AnActionEvent e) { - DataContext dataContext = e.getDataContext(); - VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext); - Editor editor = CommonDataKeys.EDITOR.getData(dataContext); - Project project = CommonDataKeys.PROJECT.getData(dataContext); - if (virtualFile == null || project == null || editor == null) { - return; - } - Task task = StudyUtils.getTaskForFile(project, virtualFile); - if (task == null) return; - if (!(task instanceof TaskWithSubtasks)) { - task = convertToTaskWithSubtasks(task, project); - } - addSubtask((TaskWithSubtasks)task, project); - } - - @NotNull - private static Task convertToTaskWithSubtasks(Task task, Project project) { - final Lesson lesson = task.getLesson(); - final List list = lesson.getTaskList(); - final int i = list.indexOf(task); - task = new TaskWithSubtasks(task); - for (TaskFile taskFile : task.getTaskFiles().values()) { - taskFile.setTask(task); - } - list.set(i, task); - final VirtualFile taskDir = task.getTaskDir(project); - renameFiles(taskDir, project, -1); - String oldTaskText = task.getTaskTexts().get(EduNames.TASK); - if (oldTaskText != null) { - task.addTaskText(EduNames.TASK + EduNames.SUBTASK_MARKER + 0, oldTaskText); - task.getTaskTexts().remove(EduNames.TASK); - } - return task; - } - - public static void addSubtask(@NotNull TaskWithSubtasks task, @NotNull Project project) { - VirtualFile taskDir = task.getTaskDir(project); - if (taskDir == null) { - return; - } - createTestsForNewSubtask(project, task); - int num = task.getLastSubtaskIndex() + 1; - StudySubtaskUtils.switchStep(project, task, num, false); - task.setLastSubtaskIndex(num); - } - - private static void createTestsForNewSubtask(Project project, TaskWithSubtasks task) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator == null) { - return; - } - configurator.createTestsForNewSubtask(project, task); - } - - @Override - public void update(AnActionEvent e) { - DataContext dataContext = e.getDataContext(); - Presentation presentation = e.getPresentation(); - presentation.setEnabledAndVisible(false); - VirtualFile virtualFile = CommonDataKeys.VIRTUAL_FILE.getData(dataContext); - Project project = CommonDataKeys.PROJECT.getData(dataContext); - if (virtualFile == null || project == null) { - return; - } - if (!CCUtils.isCourseCreator(project)) { - return; - } - if (StudyUtils.getTaskForFile(project, virtualFile) != null || StudyUtils.getTask(project, virtualFile) != null) { - presentation.setEnabledAndVisible(true); - } - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPluginToggleAction.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPluginToggleAction.java deleted file mode 100644 index 3ec6948a85c4..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCPluginToggleAction.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.ToggleAction; - -public class CCPluginToggleAction extends ToggleAction { - public static final String COURSE_CREATOR_ENABLED = "Edu.CourseCreator.Enabled"; - @Override - public boolean isSelected(AnActionEvent e) { - return PropertiesComponent.getInstance().getBoolean(COURSE_CREATOR_ENABLED); - } - - @Override - public void setSelected(AnActionEvent e, boolean state) { - PropertiesComponent.getInstance().setValue(COURSE_CREATOR_ENABLED, state); - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCProjectViewActionGroup.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCProjectViewActionGroup.java deleted file mode 100644 index e57283c54315..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCProjectViewActionGroup.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.project.DumbAware; -import com.jetbrains.edu.coursecreator.CCUtils; - -public class CCProjectViewActionGroup extends DefaultActionGroup implements DumbAware { - @Override - public void update(AnActionEvent e) { - CCUtils.updateActionGroup(e); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java deleted file mode 100644 index 067a0b47d443..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCShowPreview.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2000-2014 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.coursecreator.actions; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diff.impl.util.LabeledEditor; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.FrameWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiFile; -import com.intellij.testFramework.LightVirtualFile; -import com.intellij.ui.JBColor; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter; -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.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Collections; - -public class CCShowPreview extends DumbAwareAction { - public static final String SHOW_PREVIEW = "Show Preview"; - public static final String NO_PREVIEW_MESSAGE = "Preview is available for task files with answer placeholders only"; - - public CCShowPreview() { - super(SHOW_PREVIEW, SHOW_PREVIEW, null); - } - - @Override - public void update(@NotNull AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabledAndVisible(false); - Project project = e.getProject(); - if (project == null) { - return; - } - if (!CCUtils.isCourseCreator(project)) { - return; - } - final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); - if (file != null && StudyUtils.getTaskFile(project, file.getVirtualFile()) != null) { - presentation.setEnabledAndVisible(true); - } - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final Project project = e.getProject(); - Module module = LangDataKeys.MODULE.getData(e.getDataContext()); - if (project == null || module == null) { - return; - } - final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); - if (file == null) { - return; - } - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - VirtualFile virtualFile = file.getVirtualFile(); - TaskFile taskFile = StudyUtils.getTaskFile(project, virtualFile); - if (taskFile == null) { - return; - } - final PsiDirectory taskDir = file.getContainingDirectory(); - if (taskDir == null) { - return; - } - PsiDirectory lessonDir = taskDir.getParentDirectory(); - if (lessonDir == null) { - return; - } - - if (taskFile.getActivePlaceholders().isEmpty()) { - Messages.showInfoMessage("Preview is available for task files with answer placeholders only", "No Preview for This File"); - return; - } - - final Task task = taskFile.getTask(); - ApplicationManager.getApplication().runWriteAction(() -> { - TaskFile studentTaskFile = EduUtils.createStudentFile(project, virtualFile, task.copy(), - task instanceof TaskWithSubtasks ? ((TaskWithSubtasks)task).getActiveSubtaskIndex() : 0); - if (studentTaskFile != null) { - showPreviewDialog(project, studentTaskFile); - } - }); - } - - private static void showPreviewDialog(@NotNull Project project, @NotNull TaskFile taskFile) { - final FrameWrapper showPreviewFrame = new FrameWrapper(project); - final LightVirtualFile userFile = new LightVirtualFile(taskFile.name, taskFile.text); - showPreviewFrame.setTitle(userFile.getName()); - LabeledEditor labeledEditor = new LabeledEditor(null); - final EditorFactory factory = EditorFactory.getInstance(); - Document document = FileDocumentManager.getInstance().getDocument(userFile); - if (document == null) { - return; - } - final EditorEx createdEditor = (EditorEx)factory.createEditor(document, project, userFile, true); - Disposer.register(project, () -> factory.releaseEditor(createdEditor)); - for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) { - if (answerPlaceholder.getActiveSubtaskInfo().isNeedInsertText()) { - answerPlaceholder.setLength(answerPlaceholder.getTaskText().length()); - } - Integer minIndex = Collections.min(answerPlaceholder.getSubtaskInfos().keySet()); - answerPlaceholder.setUseLength(minIndex >= answerPlaceholder.getActiveSubtaskIndex()); - EduAnswerPlaceholderPainter.drawAnswerPlaceholder(createdEditor, answerPlaceholder, JBColor.BLUE); - } - JPanel header = new JPanel(); - header.setLayout(new BoxLayout(header, BoxLayout.Y_AXIS)); - header.setBorder(new EmptyBorder(10, 10, 10, 10)); - header.add(new JLabel("Read-only preview.")); - String timeStamp = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime()); - header.add(new JLabel(String.format("Created %s.", timeStamp))); - JComponent editorComponent = createdEditor.getComponent(); - labeledEditor.setComponent(editorComponent, header); - createdEditor.setCaretVisible(false); - createdEditor.setCaretEnabled(false); - showPreviewFrame.setComponent(labeledEditor); - showPreviewFrame.setSize(new Dimension(500, 500)); - if (!ApplicationManager.getApplication().isUnitTestMode()) { - showPreviewFrame.show(); - } - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCSwitchSubtask.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCSwitchSubtask.java deleted file mode 100644 index f2cc1a368655..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCSwitchSubtask.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.coursecreator.CCSubtaskEditorNotificationProvider; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; - -public class CCSwitchSubtask extends DumbAwareAction { - public CCSwitchSubtask() { - super("Switch Subtask", "Switches to selected subtask", null); - } - - @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - if (project == null) { - return; - } - TaskWithSubtasks task = getTask(e); - if (task != null) { - CCSubtaskEditorNotificationProvider.createPopup(task, project).showCenteredInCurrentWindow(project); - } - } - - @Override - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabledAndVisible(false); - Task task = getTask(e); - if (task != null) { - presentation.setEnabledAndVisible(true); - } - } - - private static TaskWithSubtasks getTask(@NotNull AnActionEvent e) { - Project project = e.getProject(); - if (project == null || !CCUtils.isCourseCreator(project)) { - return null; - } - VirtualFile virtualFile = e.getData(CommonDataKeys.VIRTUAL_FILE); - if (virtualFile == null) { - return null; - } - while (virtualFile.getName().equals(EduNames.SRC) || !virtualFile.isDirectory()) { - VirtualFile parent = virtualFile.getParent(); - if (parent != null) { - virtualFile = parent; - } - } - final Task task = StudyUtils.getTask(project, virtualFile); - return task instanceof TaskWithSubtasks ? (TaskWithSubtasks)task : null; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionBase.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionBase.java deleted file mode 100644 index df4afdbb34a2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionBase.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -public abstract class CCTaskFileActionBase extends AnAction { - public CCTaskFileActionBase(@Nullable String text) { - super(text); - } - - @Override - public void actionPerformed(final AnActionEvent e) { - Project project = e.getProject(); - if (project == null) { - return; - } - final VirtualFile[] virtualFiles = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(e.getDataContext()); - if (virtualFiles == null) { - return; - } - - for (VirtualFile file : virtualFiles) { - if (!isAvailable(project, file)) continue; - VirtualFile taskVF = StudyUtils.getTaskDir(file); - if (taskVF == null) { - return; - } - Task task = StudyUtils.getTask(project, taskVF); - if (task == null) { - return; - } - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (file.isDirectory()) { - final List children = VfsUtil.collectChildrenRecursively(file); - for (VirtualFile child : children) { - performAction(child, task, course, project); - } - } - else { - performAction(file, task, course, project); - } - } - } - - protected abstract void performAction(VirtualFile file, Task task, Course course, Project project); - - - @Override - public void update(AnActionEvent e) { - Project project = e.getProject(); - Presentation presentation = e.getPresentation(); - if (project == null || !CCUtils.isCourseCreator(project)) { - presentation.setEnabledAndVisible(false); - return; - } - final VirtualFile[] virtualFiles = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(e.getDataContext()); - if (virtualFiles == null || virtualFiles.length == 0) { - presentation.setEnabledAndVisible(false); - } - } - - protected abstract boolean isAvailable(Project project, VirtualFile file); -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCActivatePlaceholder.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCActivatePlaceholder.java deleted file mode 100644 index da9a77582fc8..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCActivatePlaceholder.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.intellij.openapi.util.TextRange; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; - -public class CCActivatePlaceholder extends CCSubtaskPlaceholderAction { - - public static final String TITLE = "Activate"; - public static final String ACTION_ID = "CC.ActivatePlaceholder"; - - protected CCActivatePlaceholder() { - super(TITLE, TITLE); - } - - @Override - protected AnswerPlaceholderSubtaskInfo getInfo(@NotNull CCState state, - int subtaskIndex, - @NotNull AnswerPlaceholder existingPlaceholder) { - int visibleLength = existingPlaceholder.getVisibleLength(subtaskIndex); - int placeholderOffset = existingPlaceholder.getOffset(); - String possibleAnswer = state.getEditor().getDocument().getText(TextRange.create(placeholderOffset, placeholderOffset + visibleLength)); - AnswerPlaceholderSubtaskInfo info = new AnswerPlaceholderSubtaskInfo(); - info.setPossibleAnswer(possibleAnswer); - return info; - } - - @Override - protected String getTitle() { - return TITLE; - } - - @Override - protected void redoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info) { - existingPlaceholder.getSubtaskInfos().put(subtaskIndex, info); - } - - @Override - protected void undoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info) { - existingPlaceholder.getSubtaskInfos().remove(subtaskIndex); - } - - @Override - protected boolean isAvailable(TaskFile taskFile, int offset) { - AnswerPlaceholder existingPlaceholder = StudyUtils.getAnswerPlaceholder(offset, taskFile.getAnswerPlaceholders()); - return existingPlaceholder != null && !existingPlaceholder.isActive(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAddAnswerPlaceholder.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAddAnswerPlaceholder.java deleted file mode 100644 index 882e1026426b..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAddAnswerPlaceholder.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.command.undo.BasicUndoableAction; -import com.intellij.openapi.command.undo.UnexpectedUndoException; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.SelectionModel; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.JBColor; -import com.intellij.util.DocumentUtil; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { - - public CCAddAnswerPlaceholder() { - super("Add Answer Placeholder", "Add/Delete answer placeholder"); - } - - - private static boolean arePlaceholdersIntersect(@NotNull final TaskFile taskFile, int start, int end) { - List answerPlaceholders = taskFile.getActivePlaceholders(); - for (AnswerPlaceholder existingAnswerPlaceholder : answerPlaceholders) { - int twStart = existingAnswerPlaceholder.getOffset(); - int twEnd = existingAnswerPlaceholder.getPossibleAnswerLength() + twStart; - if ((start >= twStart && start < twEnd) || (end > twStart && end <= twEnd) || - (twStart >= start && twStart < end) || (twEnd > start && twEnd <= end)) { - return true; - } - } - return false; - } - - private void addPlaceholder(@NotNull CCState state) { - Editor editor = state.getEditor(); - Project project = state.getProject(); - Document document = editor.getDocument(); - FileDocumentManager.getInstance().saveDocument(document); - final SelectionModel model = editor.getSelectionModel(); - final int offset = model.hasSelection() ? model.getSelectionStart() : editor.getCaretModel().getOffset(); - TaskFile taskFile = state.getTaskFile(); - final Task task = state.getTaskFile().getTask(); - int subtaskIndex = task instanceof TaskWithSubtasks ? ((TaskWithSubtasks)task).getActiveSubtaskIndex() : 0; - final AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder(); - AnswerPlaceholderSubtaskInfo info = new AnswerPlaceholderSubtaskInfo(); - answerPlaceholder.getSubtaskInfos().put(subtaskIndex, info); - int index = taskFile.getAnswerPlaceholders().size(); - answerPlaceholder.setIndex(index); - answerPlaceholder.setTaskFile(taskFile); - taskFile.sortAnswerPlaceholders(); - answerPlaceholder.setOffset(offset); - answerPlaceholder.setUseLength(false); - - String defaultPlaceholderText = "type here"; - CCCreateAnswerPlaceholderDialog dlg = createDialog(project, answerPlaceholder); - if (!dlg.showAndGet()) { - return; - } - String answerPlaceholderText = dlg.getTaskText(); - answerPlaceholder.setPossibleAnswer(model.hasSelection() ? model.getSelectedText() : defaultPlaceholderText); - answerPlaceholder.setTaskText(StringUtil.notNullize(answerPlaceholderText)); - answerPlaceholder.setLength(StringUtil.notNullize(answerPlaceholderText).length()); - answerPlaceholder.setHints(dlg.getHints()); - - if (!model.hasSelection()) { - DocumentUtil.writeInRunUndoTransparentAction(() -> document.insertString(offset, defaultPlaceholderText)); - } - - answerPlaceholder.setPossibleAnswer(model.hasSelection() ? model.getSelectedText() : defaultPlaceholderText); - AddAction action = new AddAction(answerPlaceholder, taskFile, editor); - EduUtils.runUndoableAction(project, "Add Answer Placeholder", action); - } - - static class AddAction extends BasicUndoableAction { - private final AnswerPlaceholder myPlaceholder; - private final TaskFile myTaskFile; - private final Editor myEditor; - - public AddAction(AnswerPlaceholder placeholder, TaskFile taskFile, Editor editor) { - super(editor.getDocument()); - myPlaceholder = placeholder; - myTaskFile = taskFile; - myEditor = editor; - } - - @Override - public void undo() throws UnexpectedUndoException { - final List answerPlaceholders = myTaskFile.getAnswerPlaceholders(); - if (answerPlaceholders.contains(myPlaceholder)) { - answerPlaceholders.remove(myPlaceholder); - myEditor.getMarkupModel().removeAllHighlighters(); - StudyUtils.drawAllAnswerPlaceholders(myEditor, myTaskFile); - EduAnswerPlaceholderPainter.createGuardedBlocks(myEditor, myTaskFile); - } - } - - @Override - public void redo() throws UnexpectedUndoException { - myTaskFile.addAnswerPlaceholder(myPlaceholder); - EduAnswerPlaceholderPainter.drawAnswerPlaceholder(myEditor, myPlaceholder, JBColor.BLUE); - EduAnswerPlaceholderPainter.createGuardedBlocks(myEditor, myPlaceholder); - } - } - - @Override - protected void performAnswerPlaceholderAction(@NotNull CCState state) { - addPlaceholder(state); - } - - @Override - public void update(@NotNull AnActionEvent event) { - final Presentation presentation = event.getPresentation(); - presentation.setEnabledAndVisible(false); - - CCState state = getState(event); - if (state == null) { - return; - } - - presentation.setVisible(true); - if (canAddPlaceholder(state)) { - presentation.setEnabled(true); - } - } - - - private static boolean canAddPlaceholder(@NotNull CCState state) { - Editor editor = state.getEditor(); - SelectionModel selectionModel = editor.getSelectionModel(); - TaskFile taskFile = state.getTaskFile(); - if (selectionModel.hasSelection()) { - int start = selectionModel.getSelectionStart(); - int end = selectionModel.getSelectionEnd(); - return !arePlaceholdersIntersect(taskFile, start, end); - } - int offset = editor.getCaretModel().getOffset(); - return StudyUtils.getAnswerPlaceholder(offset, taskFile.getAnswerPlaceholders()) == null; - } - - protected CCCreateAnswerPlaceholderDialog createDialog(Project project, AnswerPlaceholder answerPlaceholder) { - String answerPlaceholderText = StringUtil.notNullize(answerPlaceholder.getTaskText()); - return new CCCreateAnswerPlaceholderDialog(project, answerPlaceholderText.isEmpty() ? "type here" : answerPlaceholderText, - answerPlaceholder.getHints()); - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAnswerPlaceholderAction.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAnswerPlaceholderAction.java deleted file mode 100644 index b6cdcb160b49..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCAnswerPlaceholderAction.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -abstract public class CCAnswerPlaceholderAction extends DumbAwareAction { - - protected CCAnswerPlaceholderAction(@Nullable String text, @Nullable String description) { - super(text, description, null); - } - - @Nullable - protected CCState getState(@NotNull AnActionEvent e) { - final Project project = e.getProject(); - if (project == null || !CCUtils.isCourseCreator(project)) { - return null; - } - final PsiFile psiFile = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); - if (psiFile == null) { - return null; - } - VirtualFile virtualFile = psiFile.getVirtualFile(); - if (virtualFile == null) { - return null; - } - final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext()); - if (editor == null) { - return null; - } - TaskFile taskFile = StudyUtils.getTaskFile(project, virtualFile); - if (taskFile == null) { - return null; - } - AnswerPlaceholder answerPlaceholder = StudyUtils.getAnswerPlaceholder(editor.getCaretModel().getOffset(), getPlaceholders(taskFile)); - return new CCState(taskFile, answerPlaceholder, psiFile, editor, project); - } - - protected List getPlaceholders(@NotNull TaskFile taskFile) { - return taskFile.getActivePlaceholders(); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - CCState state = getState(e); - if (state == null) { - return; - } - performAnswerPlaceholderAction(state); - } - - protected abstract void performAnswerPlaceholderAction(@NotNull final CCState state); - - public static class CCState { - private TaskFile myTaskFile; - private AnswerPlaceholder myAnswerPlaceholder; - private PsiFile myFile; - private Editor myEditor; - private Project myProject; - - public CCState(@NotNull final TaskFile taskFile, - @Nullable final AnswerPlaceholder answerPlaceholder, - @NotNull final PsiFile file, - @NotNull final Editor editor, - @NotNull final Project project) { - myTaskFile = taskFile; - myAnswerPlaceholder = answerPlaceholder; - myFile = file; - myEditor = editor; - myProject = project; - } - - @NotNull - public TaskFile getTaskFile() { - return myTaskFile; - } - - @Nullable - public AnswerPlaceholder getAnswerPlaceholder() { - return myAnswerPlaceholder; - } - - @NotNull - public PsiFile getFile() { - return myFile; - } - - @NotNull - public Editor getEditor() { - return myEditor; - } - - @NotNull - public Project getProject() { - return myProject; - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCChangePlaceholderVisibility.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCChangePlaceholderVisibility.java deleted file mode 100644 index e17f128abde4..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCChangePlaceholderVisibility.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.command.UndoConfirmationPolicy; -import com.intellij.openapi.command.undo.BasicUndoableAction; -import com.intellij.openapi.command.undo.UnexpectedUndoException; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.DocumentUtil; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; - -public abstract class CCChangePlaceholderVisibility extends CCAnswerPlaceholderAction { - - protected CCChangePlaceholderVisibility(@Nullable String text, @Nullable String description) { - super(text, description); - } - - @Override - protected void performAnswerPlaceholderAction(@NotNull CCState state) { - AnswerPlaceholder placeholder = state.getAnswerPlaceholder(); - if (placeholder == null) { - return; - } - EduUtils.runUndoableAction(state.getProject(), getName(), new BasicUndoableAction(state.getEditor().getDocument()) { - @Override - public void undo() throws UnexpectedUndoException { - setVisible(placeholder, isVisible(), state); - } - - @Override - public void redo() throws UnexpectedUndoException { - setVisible(placeholder, !isVisible(), state); - } - }, UndoConfirmationPolicy.REQUEST_CONFIRMATION); - } - - private void setVisible(AnswerPlaceholder placeholder, boolean visible, CCState state) { - placeholder.getActiveSubtaskInfo().setNeedInsertText(visible); - saveIndent(placeholder, state, !visible); - int length = isVisible() ? placeholder.getTaskText().length() : 0; - placeholder.setLength(length); - StudyUtils.drawAllAnswerPlaceholders(state.getEditor(), state.getTaskFile()); - } - - private static void saveIndent(AnswerPlaceholder placeholder, CCState state, boolean visible) { - Document document = state.getEditor().getDocument(); - int offset = placeholder.getOffset(); - int lineNumber = document.getLineNumber(offset); - int nonSpaceCharOffset = DocumentUtil.getFirstNonSpaceCharOffset(document, lineNumber); - int newOffset = offset; - int endOffset = offset + placeholder.getRealLength(); - if (!visible && nonSpaceCharOffset == offset) { - newOffset = document.getLineStartOffset(lineNumber); - } - if (visible) { - newOffset = DocumentUtil.getFirstNonSpaceCharOffset(document, offset, endOffset); - } - placeholder.setOffset(newOffset); - int delta = offset - newOffset; - placeholder.setPossibleAnswer(document.getText(TextRange.create(newOffset, newOffset + delta + placeholder.getRealLength()))); - String oldTaskText = placeholder.getTaskText(); - if (delta >= 0) { - placeholder.setTaskText(StringUtil.repeat(" ", delta) + oldTaskText); - } - else { - String newTaskText = oldTaskText.substring(Math.abs(delta)); - placeholder.setTaskText(newTaskText); - } - } - - protected abstract String getName(); - - protected abstract boolean isVisible(); - - @Override - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabledAndVisible(false); - CCState state = getState(e); - if (state == null) { - return; - } - AnswerPlaceholder placeholder = state.getAnswerPlaceholder(); - if (placeholder == null) { - return; - } - Task task = state.getTaskFile().getTask(); - if ((task instanceof TaskWithSubtasks)) { - Integer minSubtaskIndex = Collections.min(placeholder.getSubtaskInfos().keySet()); - if (canChangeState(placeholder, (TaskWithSubtasks)task, minSubtaskIndex)) { - presentation.setEnabledAndVisible(true); - } - } - } - - private boolean canChangeState(@NotNull AnswerPlaceholder placeholder, @NotNull TaskWithSubtasks task, int minSubtaskIndex) { - return placeholder.isActive() && minSubtaskIndex != 0 && minSubtaskIndex == task.getActiveSubtaskIndex() && isAvailable(placeholder); - } - - protected abstract boolean isAvailable(AnswerPlaceholder placeholder); -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCCreateAnswerPlaceholderDialog.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCCreateAnswerPlaceholderDialog.java deleted file mode 100644 index e1fd3c07e115..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCCreateAnswerPlaceholderDialog.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.ValidationInfo; -import com.intellij.openapi.util.text.StringUtil; -import com.jetbrains.edu.learning.ui.CCCreateAnswerPlaceholderPanel; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.List; - -public class CCCreateAnswerPlaceholderDialog extends DialogWrapper { - - private static final String TITLE = "Add Answer Placeholder"; - private final CCCreateAnswerPlaceholderPanel myPanel; - private final Project myProject; - - public Project getProject() { - return myProject; - } - - public CCCreateAnswerPlaceholderDialog(@NotNull final Project project, - String placeholderText, - List hints) { - super(project, true); - - myProject = project; - myPanel = new CCCreateAnswerPlaceholderPanel(placeholderText, hints); - setTitle(TITLE); - setResizable(false); - init(); - initValidation(); - } - - public String getTaskText() { - return StringUtil.notNullize(myPanel.getAnswerPlaceholderText()); - } - - public List getHints() { - return myPanel.getHints(); - } - - @Nullable - @Override - protected JComponent createCenterPanel() { - return myPanel.getMailPanel(); - } - - @Nullable - @Override - public ValidationInfo doValidate() { - return null; - } - - @Nullable - @Override - public JComponent getPreferredFocusedComponent() { - return myPanel.getPreferredFocusedComponent(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeactivatePlaceholder.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeactivatePlaceholder.java deleted file mode 100644 index b28d2269681b..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeactivatePlaceholder.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; - -public class CCDeactivatePlaceholder extends CCSubtaskPlaceholderAction { - - public static final String TITLE = "Deactivate"; - - protected CCDeactivatePlaceholder() { - super(TITLE, TITLE); - } - - protected void undoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info) { - existingPlaceholder.getSubtaskInfos().put(subtaskIndex, info); - } - - @Override - protected AnswerPlaceholderSubtaskInfo getInfo(@NotNull CCState state, - int subtaskIndex, - @NotNull AnswerPlaceholder existingPlaceholder) { - return existingPlaceholder.getSubtaskInfos().get(subtaskIndex); - } - - @Override - protected String getTitle() { - return TITLE; - } - - protected void redoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info) { - existingPlaceholder.getSubtaskInfos().remove(subtaskIndex); - } - - protected boolean isAvailable(TaskFile taskFile, int offset) { - AnswerPlaceholder existingActivePlaceholder = taskFile.getAnswerPlaceholder(offset); - if (existingActivePlaceholder == null) { - return false; - } - final Task task = taskFile.getTask(); - return task instanceof TaskWithSubtasks && - Collections.min(existingActivePlaceholder.getSubtaskInfos().keySet()) < ((TaskWithSubtasks)task).getActiveSubtaskIndex(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAllAnswerPlaceholdersAction.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAllAnswerPlaceholdersAction.java deleted file mode 100644 index 4c232db34d02..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAllAnswerPlaceholdersAction.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.command.UndoConfirmationPolicy; -import com.intellij.openapi.command.undo.BasicUndoableAction; -import com.intellij.openapi.command.undo.UnexpectedUndoException; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class CCDeleteAllAnswerPlaceholdersAction extends DumbAwareAction { - - public static final String ACTION_NAME = "Delete All"; - - public CCDeleteAllAnswerPlaceholdersAction() { - super(ACTION_NAME); - } - - @Override - public void actionPerformed(AnActionEvent e) { - final DataContext context = e.getDataContext(); - final VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(context); - final Project project = e.getProject(); - if (file == null || project == null) { - return; - } - final TaskFile taskFile = StudyUtils.getTaskFile(project, file); - if (taskFile == null) { - return; - } - Editor editor = CommonDataKeys.EDITOR.getData(context); - if (editor == null) { - FileEditorManager instance = FileEditorManager.getInstance(project); - if (!instance.isFileOpen(file)) { - return; - } - FileEditor fileEditor = instance.getSelectedEditor(file); - if (!(fileEditor instanceof TextEditor)) { - return; - } - editor = ((TextEditor)fileEditor).getEditor(); - } - List placeholders = new ArrayList<>(taskFile.getAnswerPlaceholders()); - final ClearPlaceholders action = new ClearPlaceholders(taskFile, placeholders, editor); - EduUtils.runUndoableAction(project, ACTION_NAME, action, UndoConfirmationPolicy.REQUEST_CONFIRMATION); - } - - private static void updateView(@NotNull final Editor editor, - @NotNull final TaskFile taskFile) { - editor.getMarkupModel().removeAllHighlighters(); - StudyUtils.drawAllAnswerPlaceholders(editor, taskFile); - } - - @Override - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabledAndVisible(false); - - Project project = e.getProject(); - if (project == null) { - return; - } - if (!CCUtils.isCourseCreator(project)) { - return; - } - DataContext context = e.getDataContext(); - VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(context); - if (file == null ) { - return; - } - TaskFile taskFile = StudyUtils.getTaskFile(project, file); - if (taskFile == null || taskFile.getAnswerPlaceholders().isEmpty()) { - return; - } - presentation.setEnabledAndVisible(true); - } - - - private static class ClearPlaceholders extends BasicUndoableAction { - private final List myPlaceholders; - private final Editor myEditor; - private final TaskFile myTaskFile; - - public ClearPlaceholders(TaskFile taskFile, List placeholders, Editor editor) { - super(editor.getDocument()); - myTaskFile = taskFile; - myPlaceholders = placeholders; - myEditor = editor; - } - - @Override - public void undo() throws UnexpectedUndoException { - myTaskFile.getAnswerPlaceholders().addAll(myPlaceholders); - updateView(myEditor, myTaskFile); - } - - @Override - public void redo() throws UnexpectedUndoException { - myTaskFile.getAnswerPlaceholders().clear(); - updateView(myEditor, myTaskFile); - } - - @Override - public boolean isGlobal() { - return true; - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAnswerPlaceholder.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAnswerPlaceholder.java deleted file mode 100644 index e997ea2d6c20..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCDeleteAnswerPlaceholder.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.command.undo.UnexpectedUndoException; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class CCDeleteAnswerPlaceholder extends CCAnswerPlaceholderAction { - public CCDeleteAnswerPlaceholder() { - super("Delete", "Delete answer placeholder"); - } - - @Override - protected void performAnswerPlaceholderAction(@NotNull CCState state) { - deletePlaceholder(state); - } - - private static void deletePlaceholder(@NotNull CCState state) { - Project project = state.getProject(); - TaskFile taskFile = state.getTaskFile(); - AnswerPlaceholder answerPlaceholder = state.getAnswerPlaceholder(); - EduUtils.runUndoableAction(project, "Delete Answer Placeholder", - new CCAddAnswerPlaceholder.AddAction(answerPlaceholder, taskFile, state.getEditor()) { - @Override - public void undo() throws UnexpectedUndoException { - super.redo(); - } - - @Override - public void redo() throws UnexpectedUndoException { - super.undo(); - } - }); - } - - private static boolean canDeletePlaceholder(@NotNull CCState state) { - if (state.getEditor().getSelectionModel().hasSelection()) { - return false; - } - return state.getAnswerPlaceholder() != null; - } - - @Override - protected List getPlaceholders(@NotNull TaskFile taskFile) { - return taskFile.getAnswerPlaceholders(); - } - - @Override - public void update(@NotNull AnActionEvent event) { - final Presentation presentation = event.getPresentation(); - presentation.setEnabledAndVisible(false); - - CCState state = getState(event); - if (state == null) { - return; - } - - if (canDeletePlaceholder(state)) { - presentation.setEnabledAndVisible(true); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCEditAnswerPlaceholder.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCEditAnswerPlaceholder.java deleted file mode 100644 index 75d626abee99..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCEditAnswerPlaceholder.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiFile; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import org.jetbrains.annotations.NotNull; - -public class CCEditAnswerPlaceholder extends CCAnswerPlaceholderAction { - - public CCEditAnswerPlaceholder() { - super("Edit", "Edit answer placeholder"); - } - - @Override - protected void performAnswerPlaceholderAction(@NotNull CCState state) { - final Project project = state.getProject(); - PsiFile file = state.getFile(); - final PsiDirectory taskDir = file.getContainingDirectory(); - final PsiDirectory lessonDir = taskDir.getParent(); - if (lessonDir == null) return; - AnswerPlaceholder answerPlaceholder = state.getAnswerPlaceholder(); - if (answerPlaceholder == null) { - return; - } - CCCreateAnswerPlaceholderDialog dlg = new CCCreateAnswerPlaceholderDialog(project, answerPlaceholder.getTaskText(), answerPlaceholder.getHints()); - dlg.setTitle("Edit Answer Placeholder"); - if (dlg.showAndGet()) { - final String answerPlaceholderText = dlg.getTaskText(); - answerPlaceholder.setTaskText(answerPlaceholderText); - answerPlaceholder.setLength(answerPlaceholder.getActiveSubtaskInfo().isNeedInsertText() ? 0 : StringUtil.notNullize(answerPlaceholderText).length()); - answerPlaceholder.setHints(dlg.getHints()); - } - } - - @Override - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabledAndVisible(false); - CCState state = getState(e); - if (state == null || state.getAnswerPlaceholder() == null) { - return; - } - presentation.setEnabledAndVisible(true); - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCHidePlaceholderFromPrevSubtasks.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCHidePlaceholderFromPrevSubtasks.java deleted file mode 100644 index a8d5c02af9cb..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCHidePlaceholderFromPrevSubtasks.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; - -public class CCHidePlaceholderFromPrevSubtasks extends CCChangePlaceholderVisibility { - - public static final String TITLE = "Hide for Previous Subtasks"; - - public CCHidePlaceholderFromPrevSubtasks() { - super(TITLE, TITLE); - } - - @Override - protected String getName() { - return TITLE; - } - - @Override - protected boolean isVisible() { - return false; - } - - @Override - protected boolean isAvailable(AnswerPlaceholder placeholder) { - return !placeholder.getActiveSubtaskInfo().isNeedInsertText(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCMakeVisibleForPrevSubtasks.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCMakeVisibleForPrevSubtasks.java deleted file mode 100644 index e28169f8a2fb..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCMakeVisibleForPrevSubtasks.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; - -public class CCMakeVisibleForPrevSubtasks extends CCChangePlaceholderVisibility { - - public static final String TITLE = "Make Visible For Previous Subtasks"; - - protected CCMakeVisibleForPrevSubtasks() { - super(TITLE, TITLE); - } - - @Override - protected String getName() { - return TITLE; - } - - @Override - protected boolean isVisible() { - return true; - } - - @Override - protected boolean isAvailable(AnswerPlaceholder placeholder) { - return placeholder.getActiveSubtaskInfo().isNeedInsertText(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCSubtaskPlaceholderAction.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCSubtaskPlaceholderAction.java deleted file mode 100644 index 08fb4a4734a8..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/placeholder/CCSubtaskPlaceholderAction.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.placeholder; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.command.undo.BasicUndoableAction; -import com.intellij.openapi.command.undo.UnexpectedUndoException; -import com.intellij.openapi.editor.Editor; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public abstract class CCSubtaskPlaceholderAction extends CCAnswerPlaceholderAction { - protected CCSubtaskPlaceholderAction(@Nullable String text, @Nullable String description) { - super(text, description); - } - - @Override - public void performAnswerPlaceholderAction(@NotNull CCState state) { - Editor editor = state.getEditor(); - final int offset = editor.getCaretModel().getOffset(); - TaskFile taskFile = state.getTaskFile(); - final Task task = state.getTaskFile().getTask(); - if (!(task instanceof TaskWithSubtasks)) return; - - int subtaskIndex = ((TaskWithSubtasks)task).getActiveSubtaskIndex(); - AnswerPlaceholder existingPlaceholder = StudyUtils.getAnswerPlaceholder(offset, taskFile.getAnswerPlaceholders()); - if (existingPlaceholder == null) { - return; - } - AnswerPlaceholderSubtaskInfo info = getInfo(state, subtaskIndex, existingPlaceholder); - if (info == null) { - return; - } - EduUtils.runUndoableAction(state.getProject(), getTitle(), new BasicUndoableAction(state.getEditor().getDocument()) { - @Override - public void undo() throws UnexpectedUndoException { - undoAction(existingPlaceholder, subtaskIndex, info); - StudyUtils.drawAllAnswerPlaceholders(editor, taskFile); - } - - @Override - public void redo() throws UnexpectedUndoException { - redoAction(existingPlaceholder, subtaskIndex, info); - StudyUtils.drawAllAnswerPlaceholders(editor, taskFile); - } - }); - } - - protected abstract AnswerPlaceholderSubtaskInfo getInfo(@NotNull CCState state, int subtaskIndex, @NotNull AnswerPlaceholder existingPlaceholder); - - protected abstract String getTitle(); - - protected abstract void redoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info); - - protected abstract void undoAction(@NotNull AnswerPlaceholder existingPlaceholder, int subtaskIndex, @NotNull AnswerPlaceholderSubtaskInfo info); - - @Override - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabledAndVisible(false); - CCState state = getState(e); - if (state == null) { - return; - } - TaskFile taskFile = state.getTaskFile(); - if (taskFile.getTask() instanceof TaskWithSubtasks) { - int offset = state.getEditor().getCaretModel().getOffset(); - if (isAvailable(taskFile, offset)) { - presentation.setEnabledAndVisible(true); - } - } - } - - protected abstract boolean isAvailable(TaskFile taskFile, int offset); -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCGetCourseFromStepic.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCGetCourseFromStepic.java deleted file mode 100644 index 7d6cde5ec4cd..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCGetCourseFromStepic.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.stepik; - -import com.intellij.ide.IdeView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.text.StringUtil; -import com.jetbrains.edu.coursecreator.actions.CCFromCourseArchive; -import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import org.jetbrains.annotations.NotNull; - -public class CCGetCourseFromStepic extends DumbAwareAction { - - public CCGetCourseFromStepic() { - super("Get Course From Stepik", "Get Course From Stepik", null); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final IdeView view = e.getData(LangDataKeys.IDE_VIEW); - final Project project = e.getData(CommonDataKeys.PROJECT); - if (view == null || project == null) { - return; - } - final String courseId = Messages.showInputDialog("Please, enter course id", "Get Course From Stepik", null); - if (StringUtil.isNotEmpty(courseId)) { - ProgressManager.getInstance().run(new Task.Modal(project, "Creating Course", true) { - @Override - public void run(@NotNull final ProgressIndicator indicator) { - createCourse(project, courseId); - } - }); - } - } - - private static void createCourse(Project project, String courseId) { - final RemoteCourse info = CCStepicConnector.getCourseInfo(courseId); - if (info == null) return; - - final Course course = EduStepicConnector.getCourse(project, info); - if (course == null) return; - - CCFromCourseArchive.generateFromStudentCourse(project, course); - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushCourse.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushCourse.java deleted file mode 100644 index 56969c38bdbe..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushCourse.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.stepik; - -import com.intellij.ide.IdeView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class CCPushCourse extends DumbAwareAction { - public CCPushCourse() { - super("Upload Course to Stepik", "Upload Course to Stepik", null); - } - - @Override - public void update(@NotNull AnActionEvent e) { - Presentation presentation = e.getPresentation(); - Project project = e.getProject(); - presentation.setEnabledAndVisible(project != null && CCUtils.isCourseCreator(project)); - if (project != null) { - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course instanceof RemoteCourse) { - presentation.setText("Update Course on Stepik"); - } - } - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final IdeView view = e.getData(LangDataKeys.IDE_VIEW); - final Project project = e.getData(CommonDataKeys.PROJECT); - if (view == null || project == null) { - return; - } - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - if (course instanceof RemoteCourse) { - ProgressManager.getInstance().run(new Task.Modal(project, "Updating Course", true) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - indicator.setIndeterminate(false); - for (Lesson lesson : course.getLessons()) { - indicator.checkCanceled(); - indicator.setText2("Publishing lesson " + lesson.getIndex()); - - if (lesson.getId() > 0) { - CCStepicConnector.updateLesson(project, lesson); - } - else { - final int lessonId = CCStepicConnector.postLesson(project, lesson); - if (lessonId != -1) { - final List sections = ((RemoteCourse)course).getSections(); - final Integer sectionId = sections.get(sections.size() - 1); - CCStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId, project); - } - } - indicator.setFraction((double)lesson.getIndex()/course.getLessons().size()); - } - } - }); - } - else { - CCStepicConnector.postCourseWithProgress(project, course); - } - EduUsagesCollector.courseUploaded(); - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushLesson.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushLesson.java deleted file mode 100644 index 5651289f00a8..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushLesson.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.stepik; - -import com.intellij.ide.IdeView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDirectory; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.stepic.EduStepicNames; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class CCPushLesson extends DumbAwareAction { - public CCPushLesson() { - super("Update Lesson on Stepik", "Update Lesson on Stepik", null); - } - - @Override - public void update(@NotNull AnActionEvent e) { - e.getPresentation().setEnabledAndVisible(false); - final IdeView view = e.getData(LangDataKeys.IDE_VIEW); - final Project project = e.getData(CommonDataKeys.PROJECT); - if (view == null || project == null) { - return; - } - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null || !(course instanceof RemoteCourse)) { - return; - } - if (!course.getCourseMode().equals(CCUtils.COURSE_MODE)) return; - final PsiDirectory[] directories = view.getDirectories(); - if (directories.length == 0 || directories.length > 1) { - return; - } - - final PsiDirectory lessonDir = directories[0]; - if (lessonDir == null || !lessonDir.getName().contains("lesson")) { - return; - } - final Lesson lesson = course.getLesson(lessonDir.getName()); - if (lesson != null && ((RemoteCourse)course).getId() > 0) { - e.getPresentation().setEnabledAndVisible(true); - if (lesson.getId() <= 0) { - e.getPresentation().setText("Upload Lesson to Stepik"); - } - } - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final IdeView view = e.getData(LangDataKeys.IDE_VIEW); - final Project project = e.getData(CommonDataKeys.PROJECT); - if (view == null || project == null) { - return; - } - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null || !(course instanceof RemoteCourse)) { - return; - } - final PsiDirectory[] directories = view.getDirectories(); - if (directories.length == 0 || directories.length > 1) { - return; - } - - final PsiDirectory lessonDir = directories[0]; - if (lessonDir == null || !lessonDir.getName().contains("lesson")) { - return; - } - final Lesson lesson = course.getLesson(lessonDir.getName()); - if (lesson == null) { - return; - } - ProgressManager.getInstance().run(new Task.Modal(project, "Uploading Lesson", true) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - indicator.setText("Uploading lesson to " + EduStepicNames.STEPIC_URL); - if (lesson.getId() > 0) { - CCStepicConnector.updateLesson(project, lesson); - } - else { - final int lessonId = CCStepicConnector.postLesson(project, lesson); - final List sections = ((RemoteCourse)course).getSections(); - final Integer sectionId = sections.get(sections.size()-1); - CCStepicConnector.postUnit(lessonId, lesson.getIndex(), sectionId, project); - } - }}); - } - -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushTask.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushTask.java deleted file mode 100644 index 57819de5ae47..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/actions/stepik/CCPushTask.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions.stepik; - -import com.intellij.ide.IdeView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDirectory; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.stepik.CCStepicConnector; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.stepic.EduStepicNames; -import org.jetbrains.annotations.NotNull; - -public class CCPushTask extends DumbAwareAction { - public CCPushTask() { - super("Update Task on Stepik", "Update Task on Stepik", null); - } - - @Override - public void update(@NotNull AnActionEvent e) { - e.getPresentation().setEnabledAndVisible(false); - final IdeView view = e.getData(LangDataKeys.IDE_VIEW); - final Project project = e.getData(CommonDataKeys.PROJECT); - if (view == null || project == null) { - return; - } - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null || !(course instanceof RemoteCourse)) { - return; - } - if (!course.getCourseMode().equals(CCUtils.COURSE_MODE)) return; - final PsiDirectory[] directories = view.getDirectories(); - if (directories.length == 0 || directories.length > 1) { - return; - } - final PsiDirectory taskDir = directories[0]; - - if (taskDir == null || !taskDir.getName().contains(EduNames.TASK)) { - return; - } - final PsiDirectory lessonDir = taskDir.getParentDirectory(); - if (lessonDir == null) return; - final Lesson lesson = course.getLesson(lessonDir.getName()); - if (lesson != null && lesson.getId() > 0 && ((RemoteCourse)course).getId() > 0) { - e.getPresentation().setEnabledAndVisible(true); - final com.jetbrains.edu.learning.courseFormat.tasks.Task task = lesson.getTask(taskDir.getName()); - if (task.getStepId() <= 0) { - e.getPresentation().setText("Upload Task to Stepik"); - } - } - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final IdeView view = e.getData(LangDataKeys.IDE_VIEW); - final Project project = e.getData(CommonDataKeys.PROJECT); - if (view == null || project == null) { - return; - } - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - final PsiDirectory[] directories = view.getDirectories(); - if (directories.length == 0 || directories.length > 1) { - return; - } - - final PsiDirectory taskDir = directories[0]; - - if (taskDir == null || !taskDir.getName().contains(EduNames.TASK)) { - return; - } - final PsiDirectory lessonDir = taskDir.getParentDirectory(); - if (lessonDir == null) return; - final Lesson lesson = course.getLesson(lessonDir.getName()); - if (lesson == null) return; - - final com.jetbrains.edu.learning.courseFormat.tasks.Task task = lesson.getTask(taskDir.getName()); - if (task == null) return; - - ProgressManager.getInstance().run(new Task.Modal(project, "Uploading Task", true) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - indicator.setText("Uploading task to " + EduStepicNames.STEPIC_URL); - if (task.getStepId() <= 0) { - CCStepicConnector.postTask(project, task, lesson.getId()); - } - else { - CCStepicConnector.updateTask(project, task); - } - } - }); - } - -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCLessonMoveHandlerDelegate.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCLessonMoveHandlerDelegate.java deleted file mode 100644 index 801ca7a2b660..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCLessonMoveHandlerDelegate.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.jetbrains.edu.coursecreator.handlers; - -import com.intellij.ide.IdeView; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.LangDataKeys; -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.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiReference; -import com.intellij.refactoring.move.MoveCallback; -import com.intellij.refactoring.move.MoveHandlerDelegate; -import com.intellij.util.Function; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.ui.CCMoveStudyItemDialog; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; - -public class CCLessonMoveHandlerDelegate extends MoveHandlerDelegate { - - private static final Logger LOG = Logger.getInstance(CCLessonMoveHandlerDelegate.class); - - @Override - public boolean canMove(DataContext dataContext) { - if (CommonDataKeys.PSI_FILE.getData(dataContext) != null) { - return false; - } - IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext); - if (view == null) { - return false; - } - - final PsiDirectory[] directories = view.getDirectories(); - if (directories.length == 0 || directories.length > 1) { - return false; - } - - final PsiDirectory sourceDirectory = directories[0]; - return CCUtils.isLessonDir(sourceDirectory); - } - - @Override - public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) { - if (elements.length > 0 && elements[0] instanceof PsiDirectory) { - return CCUtils.isLessonDir(((PsiDirectory)elements[0])); - } - return false; - } - - @Override - public boolean isValidTarget(PsiElement psiElement, PsiElement[] sources) { - return true; - } - - @Override - public void doMove(final Project project, - PsiElement[] elements, - @Nullable PsiElement targetContainer, - @Nullable MoveCallback callback) { - if (targetContainer == null || !(targetContainer instanceof PsiDirectory)) { - return; - } - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - final PsiDirectory sourceDirectory = (PsiDirectory)elements[0]; - final Lesson sourceLesson = course.getLesson(sourceDirectory.getName()); - final Lesson targetLesson = course.getLesson(((PsiDirectory)targetContainer).getName()); - if (targetLesson == null) { - Messages.showInfoMessage("Lessons can be moved only to other lessons", "Incorrect Target For Move"); - return; - } - final CCMoveStudyItemDialog dialog = new CCMoveStudyItemDialog(project, EduNames.LESSON, targetLesson.getName()); - dialog.show(); - if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) { - return; - } - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - sourceDirectory.getVirtualFile().rename(this, "tmp"); - } - catch (IOException e) { - LOG.error(e); - } - } - }); - final VirtualFile[] lessonDirs = project.getBaseDir().getChildren(); - final Function getStudyItem = file -> course.getLesson(file.getName()); - - int sourceLessonIndex = sourceLesson.getIndex(); - sourceLesson.setIndex(-1); - CCUtils.updateHigherElements(lessonDirs, getStudyItem, sourceLessonIndex, EduNames.LESSON, -1); - - final int newItemIndex = targetLesson.getIndex() + dialog.getIndexDelta(); - - CCUtils.updateHigherElements(lessonDirs, getStudyItem, - newItemIndex - 1, EduNames.LESSON, 1); - - sourceLesson.setIndex(newItemIndex); - course.sortLessons(); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - sourceDirectory.getVirtualFile().rename(this, EduNames.LESSON + newItemIndex); - } - catch (IOException e) { - LOG.error(e); - } - } - }); - } - - @Override - public boolean tryToMove(PsiElement element, - Project project, - DataContext dataContext, - @Nullable PsiReference reference, - Editor editor) { - return CCUtils.isCourseCreator(project); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCLessonRenameHandler.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCLessonRenameHandler.java deleted file mode 100644 index ba817b628861..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCLessonRenameHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.jetbrains.edu.coursecreator.handlers; - -import com.intellij.ide.TitledHandler; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import org.jetbrains.annotations.NotNull; - -public class CCLessonRenameHandler extends CCRenameHandler implements TitledHandler { - @Override - protected boolean isAvailable(VirtualFile dir) { - return dir.getName().contains(EduNames.LESSON); - } - - @Override - protected void rename(@NotNull Project project, @NotNull Course course, @NotNull PsiDirectory directory) { - Lesson lesson = course.getLesson(directory.getName()); - if (lesson != null) { - processRename(lesson, EduNames.LESSON, project); - } - } - - @Override - public String getActionTitle() { - return "Rename lesson"; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCRenameHandler.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCRenameHandler.java deleted file mode 100644 index 0be2ff5a9aa7..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCRenameHandler.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.jetbrains.edu.coursecreator.handlers; - -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.refactoring.rename.RenameHandler; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import org.jetbrains.annotations.NotNull; - -public abstract class CCRenameHandler implements RenameHandler { - @Override - public boolean isAvailableOnDataContext(DataContext dataContext) { - PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext); - if (element == null || !(element instanceof PsiDirectory)) { - return false; - } - if (!CCUtils.isCourseCreator(element.getProject())) { - return false; - } - VirtualFile directory = ((PsiDirectory)element).getVirtualFile(); - return isAvailable(directory); - } - - protected abstract boolean isAvailable(VirtualFile directory); - - @Override - public boolean isRenaming(DataContext dataContext) { - return isAvailableOnDataContext(dataContext); - } - - @Override - public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) { - PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext); - assert element != null; - PsiDirectory directory = (PsiDirectory)element; - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - rename(project, course, directory); - ProjectView.getInstance(project).refresh(); - FileEditorManagerEx managerEx = FileEditorManagerEx.getInstanceEx(project); - for (VirtualFile virtualFile : managerEx.getOpenFiles()) { - managerEx.updateFilePresentation(virtualFile); - } - } - - protected abstract void rename(@NotNull Project project, @NotNull Course course, @NotNull PsiDirectory directory); - - - protected static void processRename(@NotNull final StudyItem item, String namePrefix, @NotNull final Project project) { - String name = item.getName(); - String text = "Rename " + StringUtil.toTitleCase(namePrefix); - String newName = Messages.showInputDialog(project, text + " '" + name + "' to", text, null, name, null); - if (newName != null) { - item.setName(newName); - } - } - - @Override - public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) { - invoke(project, null, null, dataContext); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCTaskMoveHandlerDelegate.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCTaskMoveHandlerDelegate.java deleted file mode 100644 index 0ed7f0c8b255..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCTaskMoveHandlerDelegate.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.jetbrains.edu.coursecreator.handlers; - -import com.intellij.ide.IdeView; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.LangDataKeys; -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.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiReference; -import com.intellij.refactoring.move.MoveCallback; -import com.intellij.refactoring.move.MoveHandlerDelegate; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.ui.CCMoveStudyItemDialog; -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; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -public class CCTaskMoveHandlerDelegate extends MoveHandlerDelegate { - - private static final Logger LOG = Logger.getInstance(CCTaskMoveHandlerDelegate.class); - @Override - public boolean canMove(DataContext dataContext) { - if (CommonDataKeys.PSI_FILE.getData(dataContext) != null) { - return false; - } - IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext); - if (view == null) { - return false; - } - final PsiDirectory[] directories = view.getDirectories(); - if (directories.length == 0 || directories.length > 1) { - return false; - } - - final PsiDirectory sourceDirectory = directories[0]; - return isTaskDir(sourceDirectory); - } - - @Override - public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) { - if (elements.length > 0 && elements[0] instanceof PsiDirectory) { - return isTaskDir(((PsiDirectory)elements[0])); - } - return false; - } - - private static boolean isTaskDir(PsiDirectory sourceDirectory) { - if (sourceDirectory == null) { - return false; - } - Project project = sourceDirectory.getProject(); - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null || !CCUtils.isCourseCreator(project)) { - return false; - } - return EduUtils.getTask(sourceDirectory, course) != null; - } - - @Override - public boolean isValidTarget(PsiElement psiElement, PsiElement[] sources) { - return true; - } - - @Override - public void doMove(final Project project, - PsiElement[] elements, - @Nullable PsiElement targetContainer, - @Nullable MoveCallback callback) { - if (targetContainer == null || !(targetContainer instanceof PsiDirectory)) { - return; - } - - PsiDirectory targetDir = (PsiDirectory)targetContainer; - if (!isTaskDir(targetDir) && !CCUtils.isLessonDir(targetDir)) { - Messages.showInfoMessage("Tasks can be moved only to other lessons or inside lesson", "Incorrect Target For Move"); - return; - } - final Course course = StudyTaskManager.getInstance(project).getCourse(); - final PsiDirectory sourceDirectory = (PsiDirectory)elements[0]; - - if (course == null) { - return; - } - final Task taskToMove = EduUtils.getTask(sourceDirectory, course); - if (taskToMove == null) { - return; - } - - if (CCUtils.isLessonDir(targetDir)) { - //if user moves task to any lesson, this task is inserted as the last task in this lesson - Lesson targetLesson = course.getLesson(targetDir.getName()); - if (targetLesson == null) { - return; - } - List taskList = targetLesson.getTaskList(); - moveTask(sourceDirectory, taskToMove, taskList.isEmpty() ? null : taskList.get(taskList.size() - 1), - 1, targetDir.getVirtualFile(), targetLesson); - } - else { - PsiDirectory lessonDir = targetDir.getParent(); - if (lessonDir == null) { - return; - } - Task targetTask = EduUtils.getTask(targetDir, course); - if (targetTask == null) { - return; - } - final CCMoveStudyItemDialog dialog = new CCMoveStudyItemDialog(project, EduNames.TASK, targetTask.getName()); - dialog.show(); - if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) { - return; - } - moveTask(sourceDirectory, taskToMove, targetTask, dialog.getIndexDelta(), - lessonDir.getVirtualFile(), targetTask.getLesson()); - } - - } - - private void moveTask(final PsiDirectory sourceDirectory, - final Task taskToMove, - Task targetTask, - int indexDelta, - final VirtualFile targetDirectory, - Lesson targetLesson) { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - sourceDirectory.getVirtualFile().rename(this, "tmp"); - } - catch (IOException e) { - LOG.error(e); - } - } - }); - final VirtualFile sourceLessonDir = sourceDirectory.getVirtualFile().getParent(); - if (sourceLessonDir == null) { - return; - } - CCUtils.updateHigherElements(sourceLessonDir.getChildren(), file -> taskToMove.getLesson().getTask(file.getName()), taskToMove.getIndex(), EduNames.TASK, -1); - - final int newItemIndex = targetTask != null ? targetTask.getIndex() + indexDelta : 1; - taskToMove.setIndex(-1); - taskToMove.getLesson().getTaskList().remove(taskToMove); - final Lesson finalTargetLesson = targetLesson; - CCUtils.updateHigherElements(targetDirectory.getChildren(), file -> finalTargetLesson.getTask(file.getName()), - newItemIndex - 1, EduNames.TASK, 1); - - taskToMove.setIndex(newItemIndex); - taskToMove.setLesson(targetLesson); - targetLesson.getTaskList().add(taskToMove); - Collections.sort(targetLesson.getTaskList(), EduUtils.INDEX_COMPARATOR); - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - //moving file to the same directory leads to exception - if (!targetDirectory.equals(sourceLessonDir)) { - sourceDirectory.getVirtualFile().move(this, targetDirectory); - } - sourceDirectory.getVirtualFile().rename(this, EduNames.TASK + newItemIndex); - } - catch (IOException e) { - LOG.error(e); - } - } - }); - } - - @Override - public boolean tryToMove(PsiElement element, - Project project, - DataContext dataContext, - @Nullable PsiReference reference, - Editor editor) { - return CCUtils.isCourseCreator(project); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCTaskRenameHandler.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCTaskRenameHandler.java deleted file mode 100644 index 43f201107897..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/handlers/CCTaskRenameHandler.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jetbrains.edu.coursecreator.handlers; - -import com.intellij.ide.TitledHandler; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; - -public class CCTaskRenameHandler extends CCRenameHandler implements TitledHandler { - @Override - protected boolean isAvailable(VirtualFile dir) { - if (dir.getName().contains(EduNames.TASK)) { - return true; - } - VirtualFile parent = dir.getParent(); - if (parent != null && parent.getName().contains(EduNames.TASK)) { - return true; - } - return false; - } - - @Override - protected void rename(@NotNull Project project, @NotNull Course course, @NotNull PsiDirectory directory) { - if (directory.getName().equals(EduNames.SRC)) { - directory = directory.getParent(); - if (directory == null) { - return; - } - } - PsiDirectory lessonDir = directory.getParent(); - if (lessonDir == null || !lessonDir.getName().contains(EduNames.LESSON)) { - return; - } - Lesson lesson = course.getLesson(lessonDir.getName()); - if (lesson == null) { - return; - } - String directoryName = directory.getName(); - Task task = lesson.getTask(directoryName); - if (task != null) { - processRename(task, EduNames.TASK, project); - } - } - - @Override - public String getActionTitle() { - return "Rename task"; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCModuleBuilder.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCModuleBuilder.java deleted file mode 100644 index c27c3ce622c8..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCModuleBuilder.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.jetbrains.edu.coursecreator.intellij; - -import com.intellij.ide.util.projectWizard.ModuleWizardStep; -import com.intellij.ide.util.projectWizard.ProjectWizardStepFactory; -import com.intellij.ide.util.projectWizard.SettingsStep; -import com.intellij.ide.util.projectWizard.WizardInputField; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageExtensionPoint; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.module.ModifiableModuleModel; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleWithNameAlreadyExists; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.ui.ComboBox; -import com.intellij.openapi.util.Conditions; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiManager; -import com.intellij.ui.components.JBScrollPane; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.actions.CCCreateLesson; -import com.jetbrains.edu.coursecreator.actions.CCCreateTask; -import com.jetbrains.edu.coursecreator.ui.CCNewProjectPanel; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.intellij.generation.EduCourseModuleBuilder; -import org.jdom.JDOMException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.text.JTextComponent; -import java.io.IOException; -import java.util.Collections; -import java.util.function.Function; - -class EduCCModuleBuilder extends EduCourseModuleBuilder { - private CCNewProjectPanel myPanel = new CCNewProjectPanel(); - private ComboBox myLanguageComboBox = new ComboBox(); - private static final Logger LOG = Logger.getInstance(EduCCModuleBuilder.class); - - @Nullable - @Override - public ModuleWizardStep modifySettingsStep(@NotNull SettingsStep settingsStep) { - ModuleWizardStep javaSettingsStep = - ProjectWizardStepFactory.getInstance().createJavaSettingsStep(settingsStep, this, Conditions.alwaysTrue()); - Function getValue = JTextComponent::getText; - getWizardInputField("ccname", "", "Name:", myPanel.getNameField(), getValue).addToSettings(settingsStep); - getWizardInputField("ccauthor", "", "Author:", myPanel.getAuthorField(), getValue).addToSettings(settingsStep); - - myLanguageComboBox.removeAllItems(); - for (LanguageExtensionPoint extension : Extensions.getExtensions(EduPluginConfigurator.EP_NAME, null)) { - String languageId = extension.getKey(); - Language language = Language.findLanguageByID(languageId); - if (language == null) { - LOG.info("Language with id " + languageId + " not found"); - continue; - } - myLanguageComboBox.addItem(new LanguageWrapper(language)); - } - getWizardInputField("cclang", "", "Language:", myLanguageComboBox, comboBox -> (String) comboBox.getSelectedItem()) - .addToSettings(settingsStep); - JTextArea descriptionField = myPanel.getDescriptionField(); - descriptionField.setRows(4); - descriptionField.setBorder(null); - descriptionField.invalidate(); - JBScrollPane scrollPane = new JBScrollPane(descriptionField); - scrollPane.setMinimumSize(scrollPane.getUI().getPreferredSize(descriptionField)); - getWizardInputField("ccdescr", "", "Description:", scrollPane, pane -> descriptionField.getText()).addToSettings(settingsStep); - return javaSettingsStep; - } - - @Override - public void setupRootModel(ModifiableRootModel rootModel) throws ConfigurationException { - setSourcePaths(Collections.emptyList()); - super.setupRootModel(rootModel); - } - - - @NotNull - @Override - public Module createModule(@NotNull ModifiableModuleModel moduleModel) - throws InvalidDataException, IOException, ModuleWithNameAlreadyExists, - JDOMException, ConfigurationException { - Module module = super.createModule(moduleModel); - Project project = module.getProject(); - final Course course = new Course(); - course.setName(myPanel.getName()); - course.setAuthorsAsString(myPanel.getAuthors()); - course.setDescription(myPanel.getDescription()); - LanguageWrapper wrapper = (LanguageWrapper)myLanguageComboBox.getSelectedItem(); - Language language = wrapper.getLanguage(); - course.setLanguage(language.getID()); - course.setCourseMode(CCUtils.COURSE_MODE); - StudyTaskManager.getInstance(project).setCourse(course); - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); - String languageName = language.getDisplayName(); - if (configurator == null) { - LOG.error("EduPluginConfigurator for language " + languageName + " not found"); - return module; - } - configurator.createCourseModuleContent(moduleModel, project, course, getModuleFileDirectory()); - StartupManager.getInstance(project).runWhenProjectIsInitialized(() -> new WriteCommandAction.Simple(project) { - @Override - protected void run() throws Throwable { - PsiDirectory baseDir = PsiManager.getInstance(project).findDirectory(project.getBaseDir()); - if (baseDir == null) { - return; - } - PsiDirectory lessonDir = new CCCreateLesson().createItem(null, project, baseDir, course); - if (lessonDir == null) { - LOG.error("Failed to create lesson"); - return; - } - new CCCreateTask().createItem(null, project, lessonDir, course); - } - }.execute()); - return module; - } - - @NotNull - private WizardInputField getWizardInputField(String id, - String defaultValue, - String label, - T component, - Function getValue) { - return new WizardInputField(id, defaultValue) { - @Override - public String getLabel() { - return label; - } - - @Override - public T getComponent() { - return component; - } - - @Override - public String getValue() { - return getValue.apply(component); - } - }; - } - - - private class LanguageWrapper { - private final Language myLanguage; - - LanguageWrapper(Language language) { - myLanguage = language; - } - - Language getLanguage() { - return myLanguage; - } - - @Override - public String toString() { - return myLanguage.getDisplayName(); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCProjectTemplate.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCProjectTemplate.java deleted file mode 100644 index 3e6170c92f57..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/intellij/EduCCProjectTemplate.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jetbrains.edu.coursecreator.intellij; - -import com.intellij.icons.AllIcons; -import com.intellij.ide.util.projectWizard.AbstractModuleBuilder; -import com.intellij.openapi.ui.ValidationInfo; -import com.jetbrains.edu.learning.intellij.EduIntelliJProjectTemplate; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class EduCCProjectTemplate implements EduIntelliJProjectTemplate { - @NotNull - @Override - public String getName() { - return "Create a new course"; - } - - @Nullable - @Override - public String getDescription() { - return "Assists in course creation"; - } - - @Override - public Icon getIcon() { - return AllIcons.Modules.Types.UserDefined; - } - - @NotNull - @Override - public AbstractModuleBuilder createModuleBuilder() { - return new EduCCModuleBuilder(); - } - - @Nullable - @Override - public ValidationInfo validateSettings() { - return null; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCCourseDirectoryNode.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCCourseDirectoryNode.java deleted file mode 100644 index d058b63f199f..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCCourseDirectoryNode.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.jetbrains.edu.coursecreator.projectView; - -import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.ide.projectView.impl.nodes.PsiFileNode; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.ui.JBColor; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import com.jetbrains.edu.learning.projectView.CourseDirectoryNode; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class CCCourseDirectoryNode extends CourseDirectoryNode { - - public CCCourseDirectoryNode(@NotNull Project project, - PsiDirectory value, - ViewSettings viewSettings, - @NotNull Course course) { - super(project, value, viewSettings, course); - } - - @Nullable - @Override - public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { - AbstractTreeNode node = super.modifyChildNode(childNode); - if (node != null) { - return node; - } - if (childNode instanceof PsiFileNode) { - VirtualFile virtualFile = ((PsiFileNode)childNode).getVirtualFile(); - if (virtualFile == null) { - return null; - } - if (FileUtilRt.getExtension(virtualFile.getName()).equals("iml")) { - return null; - } - return new CCStudentInvisibleFileNode(myProject, ((PsiFileNode)childNode).getValue(), myViewSettings); - } - return null; - } - - @Override - public PsiDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) { - return new CCLessonDirectoryNode(myProject, directory, myViewSettings, ((Lesson)item)); - } - - @Override - protected void updateImpl(PresentationData data) { - updatePresentation(data, myCourse.getName(), JBColor.black, EducationalCoreIcons.Course, "Course Creation"); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCDirectoryNode.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCDirectoryNode.java deleted file mode 100644 index b85c3f38d7d7..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCDirectoryNode.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.jetbrains.edu.coursecreator.projectView; - -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import com.jetbrains.edu.learning.projectView.DirectoryNode; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class CCDirectoryNode extends DirectoryNode { - public CCDirectoryNode(@NotNull Project project, - PsiDirectory value, - ViewSettings viewSettings) { - super(project, value, viewSettings); - } - - @Override - public boolean canNavigate() { - return true; - } - - @Nullable - @Override - public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { - final AbstractTreeNode node = super.modifyChildNode(childNode); - if (node != null) return node; - Object value = childNode.getValue(); - if (value instanceof PsiElement) { - PsiFile psiFile = ((PsiElement) value).getContainingFile(); - VirtualFile virtualFile = psiFile.getVirtualFile(); - - Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course == null) { - return null; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator == null) { - return new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings); - } - if (!CCUtils.isTestsFile(myProject, virtualFile)) { - return new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings); - } - } - return null; - } - - @Override - public PsiDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value) { - return new CCDirectoryNode(myProject, value, myViewSettings); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCLessonDirectoryNode.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCLessonDirectoryNode.java deleted file mode 100644 index af793dc73f08..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCLessonDirectoryNode.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.jetbrains.edu.coursecreator.projectView; - -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDirectory; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.projectView.LessonDirectoryNode; -import org.jetbrains.annotations.NotNull; - -public class CCLessonDirectoryNode extends LessonDirectoryNode { - public CCLessonDirectoryNode(@NotNull Project project, - PsiDirectory value, - ViewSettings viewSettings, - @NotNull Lesson lesson) { - super(project, value, viewSettings, lesson); - } - - @Override - public PsiDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) { - return new CCTaskDirectoryNode(myProject, directory, myViewSettings, ((Task)item)); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCStudentInvisibleFileNode.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCStudentInvisibleFileNode.java deleted file mode 100644 index e163eee2b238..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCStudentInvisibleFileNode.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.jetbrains.edu.coursecreator.projectView; - -import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiFileNode; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiFile; -import com.intellij.ui.SimpleTextAttributes; - -/** - * represents a file which is invisible for student in student mode - */ -public class CCStudentInvisibleFileNode extends PsiFileNode { - private final String myName; - - public CCStudentInvisibleFileNode(Project project, - PsiFile value, - ViewSettings viewSettings) { - super(project, value, viewSettings); - myName = value.getName(); - } - - public CCStudentInvisibleFileNode(Project project, - PsiFile value, - ViewSettings viewSettings, - String name) { - super(project, value, viewSettings); - myName = name; - } - - - @Override - protected void updateImpl(PresentationData data) { - super.updateImpl(data); - data.clearText(); - data.addText(myName, SimpleTextAttributes.GRAY_ATTRIBUTES); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCTaskDirectoryNode.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCTaskDirectoryNode.java deleted file mode 100644 index d819d0806e6a..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCTaskDirectoryNode.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.jetbrains.edu.coursecreator.projectView; - -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiNamedElement; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import com.jetbrains.edu.learning.projectView.TaskDirectoryNode; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class CCTaskDirectoryNode extends TaskDirectoryNode { - public CCTaskDirectoryNode(@NotNull Project project, - PsiDirectory value, - ViewSettings viewSettings, - @NotNull Task task) { - super(project, value, viewSettings, task); - } - - @Nullable - @Override - public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { - AbstractTreeNode node = super.modifyChildNode(childNode); - if (node != null) { - return node; - } - Object value = childNode.getValue(); - if (value instanceof PsiElement) { - PsiElement psiElement = (PsiElement) value; - PsiFile psiFile = psiElement.getContainingFile(); - VirtualFile virtualFile = psiFile.getVirtualFile(); - if (virtualFile == null) { - return null; - } - Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course == null) { - return null; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator == null) { - return new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings); - } - if (!CCUtils.isTestsFile(myProject, virtualFile)) { - return new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings); - } - if (!(myTask instanceof TaskWithSubtasks)) { - return new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings, getTestNodeName(configurator, psiElement)); - } - String testFileName = getTestNodeName(configurator, psiElement); - return isActiveSubtaskTest(virtualFile) ? new CCStudentInvisibleFileNode(myProject, psiFile, myViewSettings, testFileName) : null; - } - return null; - } - - @NotNull - private static String getTestNodeName(EduPluginConfigurator configurator, PsiElement psiElement) { - String defaultTestName = configurator.getTestFileName(); - if (psiElement instanceof PsiFile) { - return defaultTestName; - } - if (psiElement instanceof PsiNamedElement) { - String name = ((PsiNamedElement)psiElement).getName(); - return name != null ? name : defaultTestName; - } - return defaultTestName; - } - - private boolean isActiveSubtaskTest(VirtualFile virtualFile) { - if (!(myTask instanceof TaskWithSubtasks)) { - return true; - } - - if (!virtualFile.getName().contains(EduNames.SUBTASK_MARKER)) { - return false; - } - String nameWithoutExtension = virtualFile.getNameWithoutExtension(); - int stepMarkerStart = nameWithoutExtension.indexOf(EduNames.SUBTASK_MARKER); - int stepIndex = Integer.valueOf(nameWithoutExtension.substring(EduNames.SUBTASK_MARKER.length() + stepMarkerStart)); - return stepIndex == ((TaskWithSubtasks)myTask).getActiveSubtaskIndex(); - } - - @Override - public PsiDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value) { - return new CCDirectoryNode(myProject, value, myViewSettings); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java deleted file mode 100644 index 5dd486b48235..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/projectView/CCTreeStructureProvider.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.jetbrains.edu.coursecreator.projectView; - -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDirectory; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.projectView.CourseDirectoryNode; -import com.jetbrains.edu.learning.projectView.StudyTreeStructureProvider; -import org.jetbrains.annotations.NotNull; - -public class CCTreeStructureProvider extends StudyTreeStructureProvider { - @Override - protected boolean shouldModify(@NotNull Project project) { - return CCUtils.isCourseCreator(project); - } - - @NotNull - @Override - protected CourseDirectoryNode createCourseNode(Project project, AbstractTreeNode node, ViewSettings settings, Course course) { - return new CCCourseDirectoryNode(project, ((PsiDirectory)node.getValue()), settings, course); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.form b/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.form deleted file mode 100644 index f6b5c4ecd8c1..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.form +++ /dev/null @@ -1,76 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.java deleted file mode 100644 index 2661c903b01b..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCOptions.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.jetbrains.edu.coursecreator.settings; - -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.wm.IdeFocusManager; -import com.jetbrains.edu.learning.settings.StudyOptionsProvider; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -import static com.jetbrains.edu.coursecreator.actions.CCPluginToggleAction.COURSE_CREATOR_ENABLED; - -public class CCOptions implements StudyOptionsProvider { - private JRadioButton myHtmlRadioButton; - private JRadioButton myMarkdownRadioButton; - private JPanel myPanel; - - @Nullable - @Override - public JComponent createComponent() { - if (!PropertiesComponent.getInstance().getBoolean(COURSE_CREATOR_ENABLED)) return null; - if (CCSettings.getInstance().useHtmlAsDefaultTaskFormat()) { - myHtmlRadioButton.setSelected(true); - IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown( - () -> IdeFocusManager.getGlobalInstance().requestFocus(myHtmlRadioButton, true)); - } - else { - myMarkdownRadioButton.setSelected(true); - IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown( - () -> IdeFocusManager.getGlobalInstance().requestFocus(myMarkdownRadioButton, true)); - } - return myPanel; - } - - @Override - public boolean isModified() { - final boolean htmlAsDefaultTaskFormat = CCSettings.getInstance().useHtmlAsDefaultTaskFormat(); - return myHtmlRadioButton.isSelected() != htmlAsDefaultTaskFormat; - } - - @Override - public void apply() throws ConfigurationException { - if (isModified()) { - CCSettings.getInstance().setUseHtmlAsDefaultTaskFormat(myHtmlRadioButton.isSelected()); - } - } - - @Override - public void reset() { - createComponent(); - } - - @Override - public void disposeUIResources() { - - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCSettings.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCSettings.java deleted file mode 100644 index 6204a7e7765c..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/settings/CCSettings.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.jetbrains.edu.coursecreator.settings; - -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 org.jetbrains.annotations.Nullable; - -@State(name = "CCSettings", storages = @Storage("other.xml")) -public class CCSettings implements PersistentStateComponent { - private CCSettings.State myState = new CCSettings.State(); - - public static class State { - public boolean isHtmlDefault = true; - } - @Nullable - @Override - public State getState() { - return myState; - } - - @Override - public void loadState(State state) { - myState = state; - } - - public boolean useHtmlAsDefaultTaskFormat() { - return myState.isHtmlDefault; - } - - public void setUseHtmlAsDefaultTaskFormat(final boolean useHtml) { - myState.isHtmlDefault = useHtml; - } - - public static CCSettings getInstance() { - return ServiceManager.getService(CCSettings.class); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/stepik/CCStepicConnector.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/stepik/CCStepicConnector.java deleted file mode 100644 index 8d0b13ac66df..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/stepik/CCStepicConnector.java +++ /dev/null @@ -1,443 +0,0 @@ -package com.jetbrains.edu.coursecreator.stepik; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationType; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.StudySerializationUtils; -import com.jetbrains.edu.learning.StudySettings; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.stepic.*; -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.HttpDelete; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -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 java.io.IOException; -import java.util.Collections; -import java.util.List; - -import static com.jetbrains.edu.learning.StudyUtils.showOAuthDialog; - -public class CCStepicConnector { - private static final Logger LOG = Logger.getInstance(CCStepicConnector.class.getName()); - private static final String FAILED_TITLE = "Failed to publish "; - - private CCStepicConnector() { - } - - public static RemoteCourse getCourseInfo(String courseId) { - final String url = EduStepicNames.COURSES + "/" + courseId; - try { - final StepicWrappers.CoursesContainer coursesContainer = - EduStepicAuthorizedClient.getFromStepic(url, StepicWrappers.CoursesContainer.class); - return coursesContainer == null ? null : coursesContainer.courses.get(0); - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - return null; - } - - public static void postCourseWithProgress(final Project project, @NotNull final Course course) { - ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Modal(project, "Uploading Course", true) { - @Override - public void run(@NotNull final ProgressIndicator indicator) { - postCourse(project, course); - } - }); - } - - private static void postCourse(final Project project, @NotNull Course course) { - if (!checkIfAuthorized(project, "post course")) return; - - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - if (indicator != null) { - indicator.setText("Uploading course to " + EduStepicNames.STEPIC_URL); - indicator.setIndeterminate(false); - } - final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/courses"); - - final StepicUser currentUser = EduStepicAuthorizedClient.getCurrentUser(); - if (currentUser != null) { - final List courseAuthors = course.getAuthors(); - for (int i = 0; i < courseAuthors.size(); i++) { - if (courseAuthors.size() > i) { - final StepicUser courseAuthor = courseAuthors.get(i); - currentUser.setFirstName(courseAuthor.getFirstName()); - currentUser.setLastName(courseAuthor.getLastName()); - } - } - course.setAuthors(Collections.singletonList(currentUser)); - } - - String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course)); - request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); - - try { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) { - LOG.warn("Http client is null"); - return; - } - final CloseableHttpResponse response = client.execute(request); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - final StatusLine line = response.getStatusLine(); - EntityUtils.consume(responseEntity); - if (line.getStatusCode() != HttpStatus.SC_CREATED) { - final String message = FAILED_TITLE + "course "; - LOG.error(message + responseString); - showErrorNotification(project, FAILED_TITLE, responseString); - return; - } - final RemoteCourse postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0); - postedCourse.setLessons(course.getLessons(true)); - postedCourse.setAuthors(course.getAuthors()); - postedCourse.setCourseMode(CCUtils.COURSE_MODE); - postedCourse.setLanguage(course.getLanguageID()); - final int sectionId = postModule(postedCourse.getId(), 1, String.valueOf(postedCourse.getName()), project); - int position = 1; - for (Lesson lesson : course.getLessons()) { - if (indicator != null) { - indicator.checkCanceled(); - indicator.setText2("Publishing lesson " + lesson.getIndex()); - } - final int lessonId = postLesson(project, lesson); - postUnit(lessonId, position, sectionId, project); - if (indicator != null) { - indicator.setFraction((double)lesson.getIndex()/course.getLessons().size()); - indicator.checkCanceled(); - } - position += 1; - } - ApplicationManager.getApplication().runReadAction(() -> postAdditionalFiles(course, project, postedCourse.getId())); - StudyTaskManager.getInstance(project).setCourse(postedCourse); - showNotification(project, "Course published"); - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - } - - private static boolean checkIfAuthorized(@NotNull Project project, @NotNull String failedActionName) { - boolean isAuthorized = StudySettings.getInstance().getUser() != null; - if (!isAuthorized) { - showStepicNotification(project, NotificationType.ERROR, failedActionName); - return false; - } - return true; - } - - private static void postAdditionalFiles(Course course, @NotNull final Project project, int id) { - final Lesson lesson = CCUtils.createAdditionalLesson(course, project); - if (lesson != null) { - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - if (indicator != null) { - indicator.setText2("Publishing additional files"); - } - final int sectionId = postModule(id, 2, EduNames.PYCHARM_ADDITIONAL, project); - final int lessonId = postLesson(project, lesson); - postUnit(lessonId, 1, sectionId, project); - } - } - - public static void postUnit(int lessonId, int position, int sectionId, Project project) { - if (!checkIfAuthorized(project, "postTask")) return; - - final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.UNITS); - final StepicWrappers.UnitWrapper unitWrapper = new StepicWrappers.UnitWrapper(); - final StepicWrappers.Unit unit = new StepicWrappers.Unit(); - unit.setLesson(lessonId); - unit.setPosition(position); - unit.setSection(sectionId); - unitWrapper.setUnit(unit); - - String requestBody = new Gson().toJson(unitWrapper); - request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); - - try { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) return; - final CloseableHttpResponse response = client.execute(request); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - final StatusLine line = response.getStatusLine(); - EntityUtils.consume(responseEntity); - if (line.getStatusCode() != HttpStatus.SC_CREATED) { - LOG.error(FAILED_TITLE + responseString); - showErrorNotification(project, FAILED_TITLE, responseString); - } - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - } - - private static int postModule(int courseId, int position, @NotNull final String title, Project project) { - final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/sections"); - final StepicWrappers.Section section = new StepicWrappers.Section(); - section.setCourse(courseId); - section.setTitle(title); - section.setPosition(position); - final StepicWrappers.SectionWrapper sectionContainer = new StepicWrappers.SectionWrapper(); - sectionContainer.setSection(section); - String requestBody = new Gson().toJson(sectionContainer); - request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); - - try { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) return -1; - final CloseableHttpResponse response = client.execute(request); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - final StatusLine line = response.getStatusLine(); - EntityUtils.consume(responseEntity); - if (line.getStatusCode() != HttpStatus.SC_CREATED) { - LOG.error(FAILED_TITLE + responseString); - showErrorNotification(project, FAILED_TITLE, responseString); - return -1; - } - final StepicWrappers.Section - postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).getSections().get(0); - return postedSection.getId(); - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - return -1; - } - - public static int updateTask(@NotNull final Project project, @NotNull final Task task) { - if (!checkIfAuthorized(project, "update task")) return -1; - final Lesson lesson = task.getLesson(); - final int lessonId = lesson.getId(); - - final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + "/step-sources/" + String.valueOf(task.getStepId())); - final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation(). - registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create(); - ApplicationManager.getApplication().invokeLater(() -> { - task.addTestsTexts("tests.py", task.getTestsText(project)); - final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId)); - request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); - - try { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) return; - final CloseableHttpResponse response = client.execute(request); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - EntityUtils.consume(responseEntity); - final StatusLine line = response.getStatusLine(); - if (line.getStatusCode() != HttpStatus.SC_OK) { - final String message = "Failed to update task "; - LOG.error(message + responseString); - showErrorNotification(project, message, responseString); - } - else { - showNotification(project, "Task updated"); - } - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - }); - return -1; - } - - public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson) { - if(!checkIfAuthorized(project, "update lesson")) return -1; - - final HttpPut request = new HttpPut(EduStepicNames.STEPIC_API_URL + EduStepicNames.LESSONS + String.valueOf(lesson.getId())); - - String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson)); - request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); - - try { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) return -1; - final CloseableHttpResponse response = client.execute(request); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - final StatusLine line = response.getStatusLine(); - EntityUtils.consume(responseEntity); - if (line.getStatusCode() != HttpStatus.SC_OK) { - final String message = "Failed to update lesson "; - LOG.error(message + responseString); - showErrorNotification(project, message, responseString); - return -1; - } - else { - showNotification(project, "Lesson updated"); - } - - final Lesson postedLesson = new Gson().fromJson(responseString, RemoteCourse.class).getLessons().get(0); - for (Integer step : postedLesson.steps) { - deleteTask(step, project); - } - - for (Task task : lesson.getTaskList()) { - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - if (indicator != null) { - indicator.checkCanceled(); - } - postTask(project, task, lesson.getId()); - } - return lesson.getId(); - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - return -1; - } - - private static void showErrorNotification(@NotNull Project project, String message, String responseString) { - final JsonObject details = new JsonParser().parse(responseString).getAsJsonObject(); - final String detailString = details.get("detail").getAsString(); - final Notification notification = - new Notification("Push.course", message, detailString, NotificationType.ERROR); - notification.notify(project); - } - - private static void showNotification(@NotNull Project project, String message) { - final Notification notification = - new Notification("Push.course", message, message, NotificationType.INFORMATION); - notification.notify(project); - } - - private static void showStepicNotification(@NotNull Project project, - @NotNull NotificationType notificationType, @NotNull String failedActionName) { - String text = "Log in to Stepik to " + failedActionName; - Notification notification = new Notification("Stepik", "Failed to " + failedActionName, text, notificationType); - notification.addAction(new AnAction("Log in") { - - @Override - public void actionPerformed(AnActionEvent e) { - EduStepicConnector.doAuthorize(() -> showOAuthDialog()); - notification.expire(); - } - }); - - notification.notify(project); - } - - public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson) { - if (!checkIfAuthorized(project, "postLesson")) return -1; - - final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/lessons"); - - String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson)); - request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); - - try { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) return -1; - final CloseableHttpResponse response = client.execute(request); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - final StatusLine line = response.getStatusLine(); - EntityUtils.consume(responseEntity); - if (line.getStatusCode() != HttpStatus.SC_CREATED) { - final String message = FAILED_TITLE + "lesson "; - LOG.error(message + responseString); - showErrorNotification(project, message, responseString); - return 0; - } - final Lesson postedLesson = new Gson().fromJson(responseString, RemoteCourse.class).getLessons(true).get(0); - lesson.setId(postedLesson.getId()); - for (Task task : lesson.getTaskList()) { - final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); - if (indicator != null) { - indicator.checkCanceled(); - } - postTask(project, task, postedLesson.getId()); - } - return postedLesson.getId(); - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - return -1; - } - - public static void deleteTask(@NotNull final Integer task, Project project) { - final HttpDelete request = new HttpDelete(EduStepicNames.STEPIC_API_URL + EduStepicNames.STEP_SOURCES + task); - ApplicationManager.getApplication().invokeLater(() -> { - try { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) return; - final CloseableHttpResponse response = client.execute(request); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - EntityUtils.consume(responseEntity); - final StatusLine line = response.getStatusLine(); - if (line.getStatusCode() != HttpStatus.SC_NO_CONTENT) { - LOG.error("Failed to delete task " + responseString); - showErrorNotification(project, "Failed to delete task ", responseString); - } - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - }); - } - - public static void postTask(final Project project, @NotNull final Task task, final int lessonId) { - if (!checkIfAuthorized(project, "postTask")) return; - - final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + "/step-sources"); - final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation(). - registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create(); - ApplicationManager.getApplication().invokeLater(() -> { - final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId)); - request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); - - try { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) return; - final CloseableHttpResponse response = client.execute(request); - final StatusLine line = response.getStatusLine(); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - EntityUtils.consume(responseEntity); - if (line.getStatusCode() != HttpStatus.SC_CREATED) { - final String message = FAILED_TITLE + "task "; - LOG.error(message + responseString); - showErrorNotification(project, message, responseString); - return; - } - - final JsonObject postedTask = new Gson().fromJson(responseString, JsonObject.class); - final JsonObject stepSource = postedTask.getAsJsonArray("step-sources").get(0).getAsJsonObject(); - task.setStepId(stepSource.getAsJsonPrimitive("id").getAsInt()); - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - }); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemDialog.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemDialog.java deleted file mode 100644 index f986357dc695..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemDialog.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jetbrains.edu.coursecreator.ui; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.text.StringUtil; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class CCCreateStudyItemDialog extends DialogWrapper { - private final CCCreateStudyItemPanel myPanel; - - public CCCreateStudyItemDialog(@Nullable Project project, String itemName, String thresholdName, int thresholdIndex) { - super(project); - myPanel = new CCCreateStudyItemPanel(itemName, thresholdName, thresholdIndex); - setTitle("Create New " + StringUtil.toTitleCase(itemName)); - init(); - } - - @Nullable - @Override - protected JComponent createCenterPanel() { - return myPanel; - } - - public String getName() { - return myPanel.getItemName(); - } - - public int getIndexDelta() { - return myPanel.getIndexDelta(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemPanel.form b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemPanel.form deleted file mode 100644 index c1af900e81b3..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemPanel.form +++ /dev/null @@ -1,42 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemPanel.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemPanel.java deleted file mode 100644 index b006845318a9..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCCreateStudyItemPanel.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.jetbrains.edu.coursecreator.ui; - -import javax.swing.*; -import java.awt.*; - -public class CCCreateStudyItemPanel extends JPanel { - private final String myItemName; - private JPanel myPanel; - private JTextField myNameField; - private CCItemPositionPanel myPositionalPanel; - private final String myThresholdName; - - public CCCreateStudyItemPanel(String itemName, String thresholdName, int thresholdIndex) { - myThresholdName = thresholdName; - myItemName = itemName; - int newIndex = thresholdIndex + 1; - myNameField.setText(itemName + newIndex); - add(myPanel, BorderLayout.CENTER); - } - - private void createUIComponents() { - myPositionalPanel = new CCItemPositionPanel(myItemName, myThresholdName); - } - - public String getItemName() { - return myNameField.getText(); - } - - public int getIndexDelta() { - return myPositionalPanel.getIndexDelta(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCItemPositionPanel.form b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCItemPositionPanel.form deleted file mode 100644 index 922a21098153..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCItemPositionPanel.form +++ /dev/null @@ -1,37 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCItemPositionPanel.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCItemPositionPanel.java deleted file mode 100644 index a680c07f04c3..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCItemPositionPanel.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.jetbrains.edu.coursecreator.ui; - -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBRadioButton; - -import javax.swing.*; -import java.awt.*; - -public class CCItemPositionPanel extends JPanel { - private JPanel myPanel; - private JBRadioButton myBeforeButton; - private JBRadioButton myAfterButton; - private JBLabel mySpecifyPositionLabel; - - public CCItemPositionPanel(String itemName, String thresholdName) { - this.add(myPanel, BorderLayout.CENTER); - mySpecifyPositionLabel.setText(StringUtil.toTitleCase(itemName) + " position:"); - String postfix = "'" + thresholdName + "'"; - ButtonGroup group = new ButtonGroup(); - group.add(myBeforeButton); - group.add(myAfterButton); - myBeforeButton.setText("before " + postfix); - myAfterButton.setText("after " + postfix); - myAfterButton.setSelected(true); - } - - public int getIndexDelta() { - return myBeforeButton.isSelected() ? 0 : 1; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCMoveStudyItemDialog.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCMoveStudyItemDialog.java deleted file mode 100644 index a82fa3326608..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCMoveStudyItemDialog.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.jetbrains.edu.coursecreator.ui; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.util.text.StringUtil; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class CCMoveStudyItemDialog extends DialogWrapper { - private final CCItemPositionPanel myPanel; - - public CCMoveStudyItemDialog(@Nullable Project project, String itemName, String thresholdName) { - super(project); - myPanel = new CCItemPositionPanel(itemName, thresholdName); - setTitle("Move " + StringUtil.toTitleCase(itemName)); - init(); - } - - @Nullable - @Override - protected JComponent createCenterPanel() { - return myPanel; - } - - public int getIndexDelta() { - return myPanel.getIndexDelta(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCNewProjectPanel.form b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCNewProjectPanel.form deleted file mode 100644 index a38d52649a60..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCNewProjectPanel.form +++ /dev/null @@ -1,79 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCNewProjectPanel.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCNewProjectPanel.java deleted file mode 100644 index 7b4830c9cb43..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CCNewProjectPanel.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.jetbrains.edu.coursecreator.ui; - -import com.intellij.facet.ui.FacetValidatorsManager; -import com.intellij.openapi.ui.ComboBox; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.JBColor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; - -public class CCNewProjectPanel { - private JPanel myPanel; - private JTextArea myDescription; - private JTextField myName; - private JTextField myAuthorField; - private JLabel myLanguageLevelLabel; - private ComboBox myLanguageLevelCombobox; - private FacetValidatorsManager myValidationManager; - - - public CCNewProjectPanel() { - myLanguageLevelLabel.setVisible(false); - myLanguageLevelCombobox.setVisible(false); - final String userName = System.getProperty("user.name"); - if (userName != null) { - myAuthorField.setText(userName); - } - myName.getDocument().addDocumentListener(new MyValidator()); - myDescription.getDocument().addDocumentListener(new MyValidator()); - myAuthorField.getDocument().addDocumentListener(new MyValidator()); - - myDescription.setBorder(BorderFactory.createLineBorder(JBColor.border())); - myDescription.setFont(myAuthorField.getFont()); - } - - public CCNewProjectPanel(String name, String author, String description) { - this(); - myName.setText(name); - myAuthorField.setText(author); - myDescription.setText(description); - } - - public JPanel getMainPanel() { - return myPanel; - } - - @NotNull - public String getName() { - return StringUtil.notNullize(myName.getText()); - } - - @NotNull - public String getDescription() { - return StringUtil.notNullize(myDescription.getText()); - } - - @NotNull - public String[] getAuthors() { - return StringUtil.splitByLines(StringUtil.notNullize(myAuthorField.getText())); - } - - public void registerValidators(FacetValidatorsManager manager) { - myValidationManager = manager; - } - - private class MyValidator extends DocumentAdapter { - - @Override - protected void textChanged(DocumentEvent e) { - if (myValidationManager != null) { - myValidationManager.validate(); - } - } - } - - public JTextField getAuthorField() { - return myAuthorField; - } - - public JTextArea getDescriptionField() { - return myDescription; - } - - public JTextField getNameField() { - return myName; - } - - public JLabel getLanguageLevelLabel() { - return myLanguageLevelLabel; - } - - public ComboBox getLanguageLevelCombobox() { - return myLanguageLevelCombobox; - } - - @Nullable - public String getLanguageVersion() { - if (!myLanguageLevelCombobox.isVisible() || myLanguageLevelCombobox.getItemCount() == 0) { - return null; - } - return (String)myLanguageLevelCombobox.getSelectedItem(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchiveDialog.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchiveDialog.java deleted file mode 100644 index 538665e54864..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchiveDialog.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jetbrains.edu.coursecreator.ui; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.coursecreator.actions.CCCreateCourseArchive; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class CreateCourseArchiveDialog extends DialogWrapper { - - private CreateCourseArchivePanel myPanel; - private CCCreateCourseArchive myAction; - - public CreateCourseArchiveDialog(@NotNull final Project project, CCCreateCourseArchive action) { - super(project); - Course course = StudyTaskManager.getInstance(project).getCourse(); - assert course != null; - setTitle("Create Course Archive"); - myPanel = new CreateCourseArchivePanel(project, this, course.getName()); - myAction = action; - init(); - } - - @Nullable - @Override - protected JComponent createCenterPanel() { - return myPanel; - } - - public void enableOKAction(boolean isEnabled) { - myOKAction.setEnabled(isEnabled); - } - - @Override - protected void doOKAction() { - myAction.setZipName(myPanel.getZipName()); - myAction.setLocationDir(myPanel.getLocationPath()); - super.doOKAction(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchivePanel.form b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchivePanel.form deleted file mode 100644 index ca2414a7c282..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchivePanel.form +++ /dev/null @@ -1,79 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchivePanel.java b/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchivePanel.java deleted file mode 100644 index 1eeb2edd0b14..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/coursecreator/ui/CreateCourseArchivePanel.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.jetbrains.edu.coursecreator.ui; - -import com.intellij.icons.AllIcons; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.TextFieldWithBrowseButton; -import com.intellij.openapi.util.io.FileUtil; -import com.jetbrains.edu.learning.core.EduNames; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; - -public class CreateCourseArchivePanel extends JPanel { - private JPanel myPanel; - private JTextField myNameField; - private TextFieldWithBrowseButton myLocationField; - private JLabel myErrorIcon; - private JLabel myErrorLabel; - private CreateCourseArchiveDialog myDlg; - - public CreateCourseArchivePanel(@NotNull final Project project, CreateCourseArchiveDialog dlg, String name) { - setLayout(new BorderLayout()); - add(myPanel, BorderLayout.CENTER); - myErrorIcon.setIcon(AllIcons.Actions.Lightning); - setState(false); - myDlg = dlg; - String sanitizedName = FileUtil.sanitizeFileName(name); - myNameField.setText(sanitizedName.startsWith("_") ? EduNames.COURSE : sanitizedName); - myLocationField.setText(project.getBasePath()); - FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); - myLocationField.addBrowseFolderListener("Choose Location Folder", null, project, descriptor); - myLocationField.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - String location = myLocationField.getText(); - File file = new File(location); - if (!file.exists() || !file.isDirectory()) { - myDlg.enableOKAction(false); - setError("Invalid location"); - } - myDlg.enableOKAction(true); - } - }); - } - - private void setState(boolean isVisible) { - myErrorIcon.setVisible(isVisible); - myErrorLabel.setVisible(isVisible); - } - - private void setError(String message) { - myErrorLabel.setText(message); - setState(true); - } - - public String getZipName() { - return myNameField.getText(); - } - - public String getLocationPath() { - return myLocationField.getText(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java b/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java deleted file mode 100644 index b6378c8abce2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.ide.IdeView; -import com.intellij.ide.util.DirectoryUtil; -import com.intellij.lang.LanguageExtension; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.module.ModifiableModuleModel; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleType; -import com.intellij.openapi.module.StdModuleTypes; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.jetbrains.edu.learning.actions.*; -import com.jetbrains.edu.learning.checker.StudyTaskChecker; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; -import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -public interface EduPluginConfigurator { - String EP_NAME = "Edu.pluginConfigurator"; - LanguageExtension INSTANCE = new LanguageExtension<>(EP_NAME); - - @NotNull - String getTestFileName(); - - /** - * Default language name used to publish submissions to stepik - * @return - */ - @NotNull - default String getStepikDefaultLanguage() { - return ""; - } - - /** - * Creates content (including its directory or module) of new lesson in project - * - * @param project Parameter is used in Java and Kotlin plugins - * @param lesson Lesson to create content for. It's already properly initialized and added to course. - * @return PsiDirectory of created lesson - */ - default PsiDirectory createLessonContent(@NotNull Project project, - @NotNull Lesson lesson, - @Nullable IdeView view, - @NotNull PsiDirectory parentDirectory) { - final PsiDirectory[] lessonDirectory = new PsiDirectory[1]; - ApplicationManager.getApplication().runWriteAction(() -> { - String lessonDirName = EduNames.LESSON + lesson.getIndex(); - lessonDirectory[0] = DirectoryUtil.createSubdirectories(lessonDirName, parentDirectory, "\\/"); - }); - if (lessonDirectory[0] != null) { - if (view != null) { - view.selectElement(lessonDirectory[0]); - } - } - return lessonDirectory[0]; - } - - /** - * Creates content (including its directory or module) of new task in project - * - * @param task Task to create content for. It's already properly initialized and added to corresponding lesson. - * @return PsiDirectory of created task - */ - PsiDirectory createTaskContent(@NotNull final Project project, - @NotNull final Task task, - @Nullable final IdeView view, - @NotNull final PsiDirectory parentDirectory, - @NotNull final Course course); - - /** - * Used in educator plugin to filter files to be packed into course archive - */ - boolean excludeFromArchive(@NotNull String name); - - /** - * @return true for all the test files including tests for subtasks - */ - default boolean isTestFile(VirtualFile file) { - return false; - } - - default void createTestsForNewSubtask(@NotNull Project project, @NotNull TaskWithSubtasks task) { - } - - /** - * Used for code highlighting in Task Description tool window - * - * @return parameter for CodeMirror script. Available languages: @see <@linktourl http://codemirror.net/mode/> - */ - @NotNull - default String getDefaultHighlightingMode() { - return ""; - } - - /** - * Used for code highlighting in Task Description tool window - * Example in Edu Python plugin - */ - @NotNull - default String getLanguageScriptUrl() { - return ""; - } - - @NotNull - StudyTaskChecker getPyCharmTaskChecker(@NotNull PyCharmTask task, @NotNull Project project); - - @NotNull - default DefaultActionGroup getTaskDescriptionActionGroup() { - final DefaultActionGroup group = new DefaultActionGroup(); - String[] ids = new String[]{ - StudyCheckAction.ACTION_ID, - StudyPreviousTaskAction.ACTION_ID, - StudyNextTaskAction.ACTION_ID, - StudyRefreshTaskFileAction.ACTION_ID, - StudyShowHintAction.ACTION_ID - }; - ActionManager actionManager = ActionManager.getInstance(); - Arrays.stream(ids) - .map(actionManager::getAction) - .filter(Objects::nonNull) - .forEach(group::add); - - group.add(new StudyEditInputAction()); - return group; - } - - /** - * Configures (adds libraries for example) task module for languages that require modules - *
- * Example in Edu Kotlin plugin - */ - default void configureModule(@NotNull Module module) { - } - - /** - * Creates module structure for given course - *
- * Example in Edu Kotlin plugin - */ - default void createCourseModuleContent(@NotNull ModifiableModuleModel moduleModel, - @NotNull Project project, - @NotNull Course course, - @Nullable String moduleDir) { - StudyGenerator.createCourse(course, project.getBaseDir()); - } - - default List getBundledCoursePaths() { - return Collections.emptyList(); - } - - default EduCourseProjectGenerator getEduCourseProjectGenerator() { - return null; - } - - default ModuleType getModuleType() { - return StdModuleTypes.JAVA; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyAnswerPlaceholderExtendWordHandler.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyAnswerPlaceholderExtendWordHandler.java deleted file mode 100644 index a31b28d523ee..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyAnswerPlaceholderExtendWordHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.codeInsight.editorActions.ExtendWordSelectionHandler; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; -import java.util.List; - -public class StudyAnswerPlaceholderExtendWordHandler implements ExtendWordSelectionHandler { - - @Nullable - private static AnswerPlaceholder getAnswerPlaceholder(PsiElement e, int offset) { - PsiFile file = e.getContainingFile(); - if (file == null) { - return null; - } - VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null) { - return null; - } - TaskFile taskFile = StudyUtils.getTaskFile(e.getProject(), virtualFile); - if (taskFile == null) { - return null; - } - Document document = FileDocumentManager.getInstance().getDocument(virtualFile); - if (document == null) { - return null; - } - Editor editor = FileEditorManager.getInstance(e.getProject()).getSelectedTextEditor(); - return editor == null ? null : taskFile.getAnswerPlaceholder(offset); - } - - - @Override - public boolean canSelect(PsiElement e) { - Editor editor = FileEditorManager.getInstance(e.getProject()).getSelectedTextEditor(); - if (editor == null) { - return false; - } - return getAnswerPlaceholder(e, editor.getCaretModel().getOffset()) != null; - } - - @Override - public List select(PsiElement e, CharSequence editorText, int cursorOffset, Editor editor) { - AnswerPlaceholder placeholder = getAnswerPlaceholder(e, cursorOffset); - assert placeholder != null; - final Pair offsets = StudyUtils.getPlaceholderOffsets(placeholder, editor.getDocument()); - return Collections.singletonList(new TextRange(offsets.getFirst(), offsets.getSecond())); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyFileEditorManagerListener.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyFileEditorManagerListener.java deleted file mode 100644 index c462eedc7ac7..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyFileEditorManagerListener.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.jetbrains.edu.learning; - -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.jetbrains.edu.learning.courseFormat.tasks.ChoiceTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.editor.StudyChoiceVariantsPanel; -import com.jetbrains.edu.learning.ui.StudyToolWindow; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class StudyFileEditorManagerListener implements FileEditorManagerListener { - private final StudyToolWindow myToolWindow; - private final Project myProject; - - public StudyFileEditorManagerListener(StudyToolWindow toolWindow, Project project) { - myToolWindow = toolWindow; - myProject = project; - } - - @Override - public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { - Task task = getTask(file); - if (task != null) { - setTaskText(task.getTaskDescription()); - if (task instanceof ChoiceTask) { - final StudyChoiceVariantsPanel choicePanel = new StudyChoiceVariantsPanel((ChoiceTask)task); - myToolWindow.setBottomComponent(choicePanel); - } - else { - myToolWindow.setBottomComponent(null); - } - } - } - - @Override - public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) { - for (VirtualFile openedFile : source.getOpenFiles()) { - if (StudyUtils.getTaskFile(myProject, openedFile) != null) { - return; - } - } - myToolWindow.setEmptyText(myProject); - } - - @Override - public void selectionChanged(@NotNull FileEditorManagerEvent event) { - VirtualFile file = event.getNewFile(); - if (file != null) { - Task task = getTask(file); - setTaskText(task == null ? null : task.getTaskDescription()); - } - myToolWindow.setBottomComponent(null); - } - - @Nullable - private Task getTask(@NotNull VirtualFile file) { - return StudyUtils.getTaskForFile(myProject, file); - } - - private void setTaskText(@Nullable String text) { - if (text == null) { - myToolWindow.setEmptyText(myProject); - return; - } - myToolWindow.setTaskText(text, myProject); - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyMoveDelegate.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyMoveDelegate.java deleted file mode 100644 index abac20c57afe..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyMoveDelegate.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiReference; -import com.intellij.refactoring.move.MoveCallback; -import com.intellij.refactoring.move.MoveHandlerDelegate; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import org.jetbrains.annotations.Nullable; - -public class StudyMoveDelegate extends MoveHandlerDelegate{ - @Override - public boolean canMove(DataContext dataContext) { - return StudyUtils.canRenameOrMove(dataContext); - } - - @Override - public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) { - if (elements.length == 1) { - Project project = elements[0].getProject(); - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null || !EduNames.STUDY.equals(course.getCourseMode())) { - return false; - } - return !StudyUtils.isRenameableOrMoveable(project, course, elements[0]); - } - return false; - } - - @Override - public boolean isValidTarget(PsiElement psiElement, PsiElement[] sources) { - return true; - } - - @Override - public void doMove(final Project project, - PsiElement[] elements, - @Nullable PsiElement targetContainer, - @Nullable MoveCallback callback) { - Messages.showInfoMessage("This move operation can break the course", "Invalid Move Operation"); - } - - @Override - public boolean tryToMove(PsiElement element, - Project project, - DataContext dataContext, - @Nullable PsiReference reference, - Editor editor) { - return StudyUtils.isStudyProject(project); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java deleted file mode 100644 index 25df4580bbc9..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyProjectComponent.java +++ /dev/null @@ -1,382 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.ide.ui.UISettings; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationListener; -import com.intellij.notification.NotificationType; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.actionSystem.ex.AnActionListener; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ProjectComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.colors.EditorColorsListener; -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.ex.KeymapManagerEx; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileEvent; -import com.intellij.openapi.vfs.VirtualFileListener; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.util.containers.hash.HashMap; -import com.intellij.util.messages.MessageBusConnection; -import com.jetbrains.edu.learning.actions.StudyActionWithShortcut; -import com.jetbrains.edu.learning.actions.StudyNextWindowAction; -import com.jetbrains.edu.learning.actions.StudyPrevWindowAction; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.*; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; -import com.jetbrains.edu.learning.editor.StudyEditorFactoryListener; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.ui.StudyStepicUserWidget; -import com.jetbrains.edu.learning.ui.StudyToolWindow; -import com.jetbrains.edu.learning.ui.StudyToolWindowFactory; -import javafx.application.Platform; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static com.jetbrains.edu.learning.StudyUtils.execCancelable; -import static com.jetbrains.edu.learning.StudyUtils.navigateToStep; -import static com.jetbrains.edu.learning.stepic.EduStepicNames.STEP_ID; - - -public class StudyProjectComponent implements ProjectComponent { - private static final Logger LOG = Logger.getInstance(StudyProjectComponent.class.getName()); - private final Project myProject; - private FileCreatedByUserListener myListener; - private final Map>> myDeletedShortcuts = new HashMap<>(); - private MessageBusConnection myBusConnection; - - private StudyProjectComponent(@NotNull final Project project) { - myProject = project; - } - - @Override - public void projectOpened() { - // Check if user has javafx lib in his JDK. Now bundled JDK doesn't have this lib inside. - if (StudyUtils.hasJavaFx()) { - Platform.setImplicitExit(false); - } - - StartupManager.getInstance(myProject).runWhenProjectIsInitialized( - () -> { - Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course == null) { - LOG.warn("Opened project is with null course"); - return; - } - - if (!course.isAdaptive() && !course.isUpToDate()) { - updateAvailable(course); - } - - StudyUtils.registerStudyToolWindow(course, myProject); - addStepicWidget(); - selectStep(course); - - ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { - UISettings instance = UISettings.getInstance(); - instance.setHideToolStripes(false); - instance.fireUISettingsChanged(); - registerShortcuts(); - EduUsagesCollector.projectTypeOpened(course.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY); - })); - } - ); - - myBusConnection = ApplicationManager.getApplication().getMessageBus().connect(); - myBusConnection.subscribe(EditorColorsManager.TOPIC, new EditorColorsListener() { - @Override - public void globalSchemeChange(EditorColorsScheme scheme) { - final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(myProject); - if (toolWindow != null) { - toolWindow.updateFonts(myProject); - } - } - }); - } - - private void addStepicWidget() { - StudyStepicUserWidget widget = StudyUtils.getStepicWidget(); - if (widget == null) { - StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject); - statusBar.addWidget(new StudyStepicUserWidget(), "before Position"); - } - else { - widget.update(); - } - } - - private void selectStep(@NotNull Course course) { - int stepId = PropertiesComponent.getInstance().getInt(STEP_ID, 0); - if (stepId != 0) { - navigateToStep(myProject, course, stepId); - } - } - - private void updateAvailable(Course course) { - final Notification notification = - new Notification("Update.course", "Course Updates", "Course is ready to update", NotificationType.INFORMATION, - new NotificationListener() { - @Override - public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { - FileEditorManagerEx.getInstanceEx(myProject).closeAllFiles(); - - ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { - ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - return execCancelable(() -> { - updateCourse(); - return true; - }); - }, "Updating Course", true, myProject); - EduUtils.synchronize(); - course.setUpdated(); - } - }); - notification.notify(myProject); - } - - private void registerShortcuts() { - StudyToolWindow window = StudyUtils.getStudyToolWindow(myProject); - if (window != null) { - List actionsOnToolbar = window.getActions(true); - for (AnAction action : actionsOnToolbar) { - if (action instanceof StudyActionWithShortcut) { - String id = ((StudyActionWithShortcut)action).getActionId(); - String[] shortcuts = ((StudyActionWithShortcut)action).getShortcuts(); - if (shortcuts != null) { - addShortcut(id, shortcuts); - } - } - } - addShortcut(StudyNextWindowAction.ACTION_ID, new String[]{StudyNextWindowAction.SHORTCUT, StudyNextWindowAction.SHORTCUT2}); - addShortcut(StudyPrevWindowAction.ACTION_ID, new String[]{StudyPrevWindowAction.SHORTCUT}); - } - } - - private void updateCourse() { - final Course currentCourse = StudyTaskManager.getInstance(myProject).getCourse(); - if (currentCourse == null || !(currentCourse instanceof RemoteCourse)) return; - final Course course = EduStepicConnector.getCourse(myProject, (RemoteCourse)currentCourse); - if (course == null) return; - course.initCourse(false); - - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator == null) { - LOG.info("EduPluginConfigurator not found for language " + course.getLanguageById().getDisplayName()); - return; - } - - final ArrayList updatedLessons = new ArrayList<>(); - - int lessonIndex = 0; - for (Lesson lesson : course.getLessons(true)) { - lessonIndex += 1; - Lesson studentLesson = currentCourse.getLesson(lesson.getId()); - final String lessonDirName = EduNames.LESSON + String.valueOf(lessonIndex); - - final VirtualFile baseDir = myProject.getBaseDir(); - final VirtualFile lessonDir = baseDir.findChild(lessonDirName); - if (lessonDir == null) { - try { - StudyGenerator.createLesson(lesson, baseDir); - } - catch (IOException e) { - LOG.error("Failed to create lesson"); - } - lesson.setIndex(lessonIndex); - lesson.initLesson(currentCourse, false); - for (int i = 1; i <= lesson.getTaskList().size(); i++) { - Task task = lesson.getTaskList().get(i - 1); - task.setIndex(i); - } - updatedLessons.add(lesson); - continue; - } - studentLesson.setIndex(lessonIndex); - updatedLessons.add(studentLesson); - - int index = 0; - final ArrayList tasks = new ArrayList<>(); - for (Task task : lesson.getTaskList()) { - index += 1; - final Task studentTask = studentLesson.getTask(task.getStepId()); - if (studentTask != null && StudyStatus.Solved.equals(studentTask.getStatus())) { - studentTask.setIndex(index); - tasks.add(studentTask); - continue; - } - task.initTask(studentLesson, false); - task.setIndex(index); - - final String taskDirName = EduNames.TASK + String.valueOf(index); - final VirtualFile taskDir = lessonDir.findChild(taskDirName); - - if (taskDir != null) return; - try { - StudyGenerator.createTask(task, lessonDir); - } - catch (IOException e) { - LOG.error("Failed to create task"); - } - tasks.add(task); - } - studentLesson.updateTaskList(tasks); - } - currentCourse.setLessons(updatedLessons); - - final Notification notification = - new Notification("Update.course", "Course update", "Current course is synchronized", NotificationType.INFORMATION); - notification.notify(myProject); - } - - private void addShortcut(@NotNull final String actionIdString, @NotNull final String[] shortcuts) { - KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx(); - for (Keymap keymap : keymapManager.getAllKeymaps()) { - List> pairs = myDeletedShortcuts.get(keymap); - if (pairs == null) { - pairs = new ArrayList<>(); - myDeletedShortcuts.put(keymap, pairs); - } - for (String shortcutString : shortcuts) { - Shortcut studyActionShortcut = new KeyboardShortcut(KeyStroke.getKeyStroke(shortcutString), null); - String[] actionsIds = keymap.getActionIds(studyActionShortcut); - for (String actionId : actionsIds) { - pairs.add(Pair.create(actionId, shortcutString)); - keymap.removeShortcut(actionId, studyActionShortcut); - } - keymap.addShortcut(actionIdString, studyActionShortcut); - } - } - } - - @Override - public void projectClosed() { - final Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course != null) { - final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW); - if (toolWindow != null) { - toolWindow.getContentManager().removeAllContents(false); - } - KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx(); - for (Keymap keymap : keymapManager.getAllKeymaps()) { - List> pairs = myDeletedShortcuts.get(keymap); - if (pairs != null && !pairs.isEmpty()) { - for (Pair actionShortcut : pairs) { - keymap.addShortcut(actionShortcut.first, new KeyboardShortcut(KeyStroke.getKeyStroke(actionShortcut.second), null)); - } - } - } - } - myListener = null; - } - - @Override - public void initComponent() { - EditorFactory.getInstance().addEditorFactoryListener(new StudyEditorFactoryListener(), myProject); - ActionManager.getInstance().addAnActionListener(new AnActionListener() { - @Override - public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { - AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null); - for (AnAction newAction : newGroupActions) { - if (newAction == action) { - myListener = new FileCreatedByUserListener(); - VirtualFileManager.getInstance().addVirtualFileListener(myListener); - break; - } - } - } - - @Override - public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { - AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null); - for (AnAction newAction : newGroupActions) { - if (newAction == action) { - VirtualFileManager.getInstance().removeVirtualFileListener(myListener); - } - } - } - - @Override - public void beforeEditorTyping(char c, DataContext dataContext) { - - } - }); - } - - @Override - public void disposeComponent() { - if (myBusConnection != null) { - myBusConnection.disconnect(); - } - } - - @NotNull - @Override - public String getComponentName() { - return "StudyTaskManager"; - } - - public static StudyProjectComponent getInstance(@NotNull final Project project) { - final Module module = ModuleManager.getInstance(project).getModules()[0]; - return module.getComponent(StudyProjectComponent.class); - } - - private class FileCreatedByUserListener implements VirtualFileListener { - @Override - public void fileCreated(@NotNull VirtualFileEvent event) { - if (myProject.isDisposed()) return; - final VirtualFile createdFile = event.getFile(); - final VirtualFile taskDir = StudyUtils.getTaskDir(createdFile); - final Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course == null || !EduNames.STUDY.equals(course.getCourseMode())) { - return; - } - if (taskDir != null && taskDir.getName().contains(EduNames.TASK)) { - int taskIndex = EduUtils.getIndex(taskDir.getName(), EduNames.TASK); - final VirtualFile lessonDir = taskDir.getParent(); - if (lessonDir != null && lessonDir.getName().contains(EduNames.LESSON)) { - int lessonIndex = EduUtils.getIndex(lessonDir.getName(), EduNames.LESSON); - List lessons = course.getLessons(); - if (StudyUtils.indexIsValid(lessonIndex, lessons)) { - final Lesson lesson = lessons.get(lessonIndex); - final List tasks = lesson.getTaskList(); - if (StudyUtils.indexIsValid(taskIndex, tasks)) { - final Task task = tasks.get(taskIndex); - final TaskFile taskFile = new TaskFile(); - taskFile.initTaskFile(task, false); - taskFile.setUserCreated(true); - final String name = FileUtil.getRelativePath(taskDir.getPath(), createdFile.getPath(), '/'); - taskFile.name = name; - //TODO: put to other steps as well - task.getTaskFiles().put(name, taskFile); - } - } - } - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyRenameHandler.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyRenameHandler.java deleted file mode 100644 index 9ec8201d6758..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyRenameHandler.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.refactoring.rename.RenameHandler; -import org.jetbrains.annotations.NotNull; - -public class StudyRenameHandler implements RenameHandler { - @Override - public boolean isAvailableOnDataContext(DataContext dataContext) { - return StudyUtils.canRenameOrMove(dataContext); - } - - @Override - public boolean isRenaming(DataContext dataContext) { - return isAvailableOnDataContext(dataContext); - } - - @Override - public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) { - Messages.showInfoMessage("This rename operation can break the course", "Invalid Rename Operation"); - } - - @Override - public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) { - invoke(project, null, null, dataContext); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudySerializationUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/StudySerializationUtils.java deleted file mode 100644 index 4df9b363d6e6..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudySerializationUtils.java +++ /dev/null @@ -1,776 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.google.gson.*; -import com.google.gson.reflect.TypeToken; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.hash.HashMap; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.*; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicWrappers; -import org.jdom.Attribute; -import org.jdom.Content; -import org.jdom.Element; -import org.jdom.output.XMLOutputter; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.lang.reflect.Type; -import java.util.*; -import java.util.stream.Collectors; - -public class StudySerializationUtils { - private static final Logger LOG = Logger.getInstance(StudySerializationUtils.class); - - public static final String PLACEHOLDERS = "placeholders"; - public static final String LINE = "line"; - public static final String START = "start"; - public static final String LENGTH = "length"; - public static final String POSSIBLE_ANSWER = "possible_answer"; - public static final String HINT = "hint"; - public static final String ADDITIONAL_HINTS = "additional_hints"; - public static final String OFFSET = "offset"; - public static final String TEXT = "text"; - public static final String LESSONS = "lessons"; - public static final String COURSE = "course"; - public static final String ID = "id"; - public static final String COURSE_TITLED = "Course"; - public static final String STATUS = "status"; - public static final String AUTHOR = "author"; - public static final String AUTHORS = "authors"; - public static final String MY_INITIAL_START = "myInitialStart"; - - private StudySerializationUtils() { - } - - public static class StudyUnrecognizedFormatException extends Exception { - } - - public static class Xml { - public final static String COURSE_ELEMENT = "courseElement"; - public final static String MAIN_ELEMENT = "StudyTaskManager"; - public final static String REMOTE_COURSE = "RemoteCourse"; - public static final String MAP = "map"; - public static final String KEY = "key"; - public static final String VALUE = "value"; - public static final String NAME = "name"; - public static final String LIST = "list"; - public static final String OPTION = "option"; - public static final String INDEX = "index"; - public static final String STUDY_STATUS_MAP = "myStudyStatusMap"; - public static final String TASK_STATUS_MAP = "myTaskStatusMap"; - public static final String LENGTH = "length"; - public static final String ANSWER_PLACEHOLDERS = "answerPlaceholders"; - public static final String TASK_LIST = "taskList"; - public static final String TASK_FILES = "taskFiles"; - public static final String INITIAL_STATE = "initialState"; - public static final String MY_INITIAL_STATE = "MyInitialState"; - public static final String MY_LINE = "myLine"; - public static final String MY_START = "myStart"; - public static final String MY_LENGTH = "myLength"; - public static final String HINT = "hint"; - public static final String AUTHOR_TITLED = "Author"; - public static final String FIRST_NAME = "first_name"; - public static final String SECOND_NAME = "second_name"; - public static final String MY_INITIAL_LINE = "myInitialLine"; - public static final String MY_INITIAL_LENGTH = "myInitialLength"; - public static final String ANSWER_PLACEHOLDER = "AnswerPlaceholder"; - public static final String TASK_WINDOWS = "taskWindows"; - public static final String RESOURCE_PATH = "resourcePath"; - public static final String COURSE_DIRECTORY = "courseDirectory"; - public static final String SUBTASK_INFO = "AnswerPlaceholderSubtaskInfo"; - public static final String SUBTASK_INFOS = "subtaskInfos"; - public static final String ADDITIONAL_HINTS = "additionalHints"; - public static final String POSSIBLE_ANSWER = "possibleAnswer"; - public static final String SELECTED = "selected"; - public static final String TASK_TEXT = "taskText"; - public static final String PLACEHOLDER_TEXT = "placeholderText"; - private static final String TASK_HTML = "task.html"; - private static final String TASK_MD = "task.md"; - private static String LAST_SUBTASK_INDEX = "lastSubtaskIndex"; - private static String THEORY_TAG = "theoryTask"; - private static String ADAPTIVE_TASK_PARAMETERS = "adaptiveTaskParameters"; - private static String ADAPTIVE = "adaptive"; - public static final String PYCHARM_TASK = "PyCharmTask"; - private static String TASK_WITH_SUBTASKS = "TaskWithSubtasks"; - private static String THEORY_TASK = "TheoryTask"; - private static String CHOICE_TASK = "ChoiceTask"; - private static String CODE_TASK = "CodeTask"; - private static String TASK_TEXTS = "taskTexts"; - - private Xml() { - } - - public static int getVersion(Element element) throws StudyUnrecognizedFormatException { - if (element.getChild(COURSE_ELEMENT) != null) { - return 1; - } - - final Element taskManager = element.getChild(MAIN_ELEMENT); - - Element versionElement = getChildWithName(taskManager, "VERSION"); - if (versionElement == null) { - return -1; - } - - return Integer.valueOf(versionElement.getAttributeValue(VALUE)); - } - - public static Element convertToSecondVersion(Element element) throws StudyUnrecognizedFormatException { - final Element oldCourseElement = element.getChild(COURSE_ELEMENT); - Element state = new Element(MAIN_ELEMENT); - - Element course = addChildWithName(state, COURSE, oldCourseElement.clone()); - course.setName(COURSE_TITLED); - - Element author = getChildWithName(course, AUTHOR); - String authorString = author.getAttributeValue(VALUE); - course.removeContent(author); - - String[] names = authorString.split(" ", 2); - Element authorElement = new Element(AUTHOR_TITLED); - addChildWithName(authorElement, FIRST_NAME, names[0]); - addChildWithName(authorElement, SECOND_NAME, names.length == 1 ? "" : names[1]); - - addChildList(course, AUTHORS, Collections.singletonList(authorElement)); - - Element courseDirectoryElement = getChildWithName(course, RESOURCE_PATH); - renameElement(courseDirectoryElement, COURSE_DIRECTORY); - - for (Element lesson : getChildList(course, LESSONS)) { - incrementIndex(lesson); - for (Element task : getChildList(lesson, TASK_LIST)) { - incrementIndex(task); - Map taskFiles = getChildMap(task, TASK_FILES); - for (Element taskFile : taskFiles.values()) { - renameElement(getChildWithName(taskFile, TASK_WINDOWS), ANSWER_PLACEHOLDERS); - for (Element placeholder : getChildList(taskFile, ANSWER_PLACEHOLDERS)) { - placeholder.setName(ANSWER_PLACEHOLDER); - - Element initialState = new Element(MY_INITIAL_STATE); - addChildWithName(placeholder, INITIAL_STATE, initialState); - addChildWithName(initialState, MY_LINE, getChildWithName(placeholder, MY_INITIAL_LINE).getAttributeValue(VALUE)); - addChildWithName(initialState, MY_START, getChildWithName(placeholder, MY_INITIAL_START).getAttributeValue(VALUE)); - addChildWithName(initialState, MY_LENGTH, getChildWithName(placeholder, MY_INITIAL_LENGTH).getAttributeValue(VALUE)); - } - } - } - } - element.removeContent(); - element.addContent(state); - return element; - } - - public static Map fillStatusMap(Element taskManagerElement, String mapName, XMLOutputter outputter) - throws StudyUnrecognizedFormatException { - Map sourceMap = getChildMap(taskManagerElement, mapName); - Map destMap = new HashMap<>(); - for (Map.Entry entry : sourceMap.entrySet()) { - String status = entry.getValue(); - if (status.equals(StudyStatus.Unchecked.toString())) { - continue; - } - destMap.put(outputter.outputString(entry.getKey()), status); - } - return destMap; - } - - public static Element convertToThirdVersion(Element state, Project project) throws StudyUnrecognizedFormatException { - Element taskManagerElement = state.getChild(MAIN_ELEMENT); - XMLOutputter outputter = new XMLOutputter(); - - Map placeholderTextToStatus = fillStatusMap(taskManagerElement, STUDY_STATUS_MAP, outputter); - Map taskFileToStatusMap = fillStatusMap(taskManagerElement, TASK_STATUS_MAP, outputter); - - Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED); - for (Element lesson : getChildList(courseElement, LESSONS)) { - int lessonIndex = getAsInt(lesson, INDEX); - for (Element task : getChildList(lesson, TASK_LIST)) { - String taskStatus = null; - int taskIndex = getAsInt(task, INDEX); - Map taskFiles = getChildMap(task, TASK_FILES); - for (Map.Entry entry : taskFiles.entrySet()) { - Element taskFileElement = entry.getValue(); - String taskFileText = outputter.outputString(taskFileElement); - String taskFileStatus = taskFileToStatusMap.get(taskFileText); - if (taskFileStatus != null && (taskStatus == null || taskFileStatus.equals(StudyStatus.Failed.toString()))) { - taskStatus = taskFileStatus; - } - Document document = StudyUtils.getDocument(project.getBasePath(), lessonIndex, taskIndex, entry.getKey()); - if (document == null) { - continue; - } - for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) { - taskStatus = addStatus(outputter, placeholderTextToStatus, taskStatus, placeholder); - addOffset(document, placeholder); - addInitialState(document, placeholder); - } - } - if (taskStatus != null) { - addChildWithName(task, STATUS, taskStatus); - } - } - } - return state; - } - - @NotNull - public static Element convertToForthVersion(Element state) throws StudyUnrecognizedFormatException { - Element taskManagerElement = state.getChild(MAIN_ELEMENT); - Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED); - for (Element lesson : getChildList(courseElement, LESSONS)) { - for (Element task : getChildList(lesson, TASK_LIST)) { - Map taskFiles = getChildMap(task, TASK_FILES); - for (Map.Entry entry : taskFiles.entrySet()) { - Element taskFileElement = entry.getValue(); - for (Element placeholder : getChildList(taskFileElement, ANSWER_PLACEHOLDERS)) { - Element valueElement = new Element(SUBTASK_INFO); - addChildMap(placeholder, SUBTASK_INFOS, Collections.singletonMap(String.valueOf(0), valueElement)); - for (String childName : ContainerUtil.list(HINT, POSSIBLE_ANSWER, SELECTED, STATUS, TASK_TEXT)) { - Element child = getChildWithName(placeholder, childName, true); - if (child == null) { - continue; - } - valueElement.addContent(child.clone()); - } - renameElement(getChildWithName(valueElement, TASK_TEXT), PLACEHOLDER_TEXT); - Element hint = getChildWithName(valueElement, HINT); - Element firstHint = new Element(OPTION).setAttribute(VALUE, hint.getAttributeValue(VALUE)); - List newHints = new ArrayList<>(); - newHints.add(firstHint); - newHints.addAll(ContainerUtil.map(getChildList(placeholder, ADDITIONAL_HINTS, true), Element::clone)); - addChildList(valueElement, "hints", newHints); - } - } - } - } - - return state; - } - - public static Element convertToFifthVersion(Element state) throws StudyUnrecognizedFormatException { - Element taskManagerElement = state.getChild(MAIN_ELEMENT); - Element courseElement = getChildWithName(taskManagerElement, COURSE).getChild(COURSE_TITLED); - final int courseId = getAsInt(courseElement, ID); - if (courseElement != null && courseId > 0) { - courseElement.setName(REMOTE_COURSE); - } - final Element adaptive = getChildWithName(courseElement, ADAPTIVE); - for (Element lesson : getChildList(courseElement, LESSONS)) { - for (Element task : getChildList(lesson, TASK_LIST)) { - final Element lastSubtaskIndex = getChildWithName(task, LAST_SUBTASK_INDEX, true); //could be broken by 3->4 migration - final Element adaptiveParams = getChildWithName(task, ADAPTIVE_TASK_PARAMETERS, true); - Element theoryTask = getChildWithName(task, THEORY_TAG, true); - if (theoryTask == null && adaptiveParams != null) { - theoryTask = getChildWithName(adaptiveParams, THEORY_TAG, true); - } - final boolean hasAdaptiveParams = adaptiveParams != null && !adaptiveParams.getChildren().isEmpty(); - if (lastSubtaskIndex != null && Integer.valueOf(lastSubtaskIndex.getAttributeValue(VALUE)) != 0) { - task.setName(TASK_WITH_SUBTASKS); - } - else if (theoryTask != null && Boolean.valueOf(theoryTask.getAttributeValue(VALUE))) { - task.setName(THEORY_TASK); - } - else if (hasAdaptiveParams) { - task.setName(CHOICE_TASK); - final Element adaptiveParameters = adaptiveParams.getChildren().get(0); - for (Element element : adaptiveParameters.getChildren()) { - final Attribute name = element.getAttribute(NAME); - if (name != null && !THEORY_TAG.equals(name.getValue())) { - final Content elementCopy = element.clone(); - task.addContent(elementCopy); - } - } - } - else if (Boolean.valueOf(adaptive.getAttributeValue(VALUE))) { - task.setName(CODE_TASK); - } - else { - task.setName(PYCHARM_TASK); - } - task.removeContent(adaptiveParams); - task.removeContent(theoryTask); - } - } - return state; - } - - public static Element convertToSixthVersion(Element state, Project project) throws StudyUnrecognizedFormatException { - Element taskManagerElement = state.getChild(MAIN_ELEMENT); - Element courseHolder = getChildWithName(taskManagerElement, COURSE); - Element courseElement = courseHolder.getChild(COURSE_TITLED); - if (courseElement == null) { - courseElement = courseHolder.getChild(REMOTE_COURSE); - if (courseElement == null) { - throw new StudyUnrecognizedFormatException(); - } - } - for (Element lesson : getChildList(courseElement, LESSONS)) { - for (Element task : getChildList(lesson, TASK_LIST)) { - VirtualFile taskDir = getTaskDir(project, lesson, task); - if (taskDir == null) { - throw new StudyUnrecognizedFormatException(); - } - List taskDescriptionFiles = Arrays.stream(taskDir.getChildren()) - .filter(file -> isTaskDescriptionFile(file.getName())) - .collect(Collectors.toList()); - Map taskTextsMap = new HashMap<>(); - for (VirtualFile file : taskDescriptionFiles) { - try { - String text = VfsUtilCore.loadText(file); - String key = FileUtil.getNameWithoutExtension(file.getName()); - if (key.equals(EduNames.TASK) && taskDescriptionFiles.size() > 1) { - taskTextsMap.put(EduNames.TASK + EduNames.SUBTASK_MARKER + 0, text); - } - else { - taskTextsMap.put(key, text); - } - } - catch (IOException e) { - LOG.error(e); - } - } - addTextChildMap(task, TASK_TEXTS, taskTextsMap); - for (VirtualFile file : taskDescriptionFiles) { - ApplicationManager.getApplication().runWriteAction(() -> { - try { - file.delete(project); - } - catch (IOException e) { - LOG.error(e); - } - }); - } - } - } - return state; - } - - public static VirtualFile getTaskDir(Project project, Element lesson, Element task) - throws StudyUnrecognizedFormatException { - VirtualFile taskDir = - project.getBaseDir().findChild(EduNames.LESSON + getAsInt(lesson, INDEX)).findChild(EduNames.TASK + getAsInt(task, INDEX)); - if (taskDir == null) { - return null; - } - VirtualFile srcDir = taskDir.findChild(EduNames.SRC); - if (srcDir != null) { - taskDir = srcDir; - } - return taskDir; - } - - public static String addStatus(XMLOutputter outputter, - Map placeholderTextToStatus, - String taskStatus, - Element placeholder) { - String placeholderText = outputter.outputString(placeholder); - String status = placeholderTextToStatus.get(placeholderText); - if (status != null) { - addChildWithName(placeholder, STATUS, status); - if (taskStatus == null || status.equals(StudyStatus.Failed.toString())) { - taskStatus = status; - } - } - return taskStatus; - } - - public static void addInitialState(Document document, Element placeholder) throws StudyUnrecognizedFormatException { - Element initialState = getChildWithName(placeholder, INITIAL_STATE).getChild(MY_INITIAL_STATE); - int initialLine = getAsInt(initialState, MY_LINE); - int initialStart = getAsInt(initialState, MY_START); - int initialOffset = document.getLineStartOffset(initialLine) + initialStart; - addChildWithName(initialState, OFFSET, initialOffset); - renameElement(getChildWithName(initialState, MY_LENGTH), LENGTH); - } - - public static void addOffset(Document document, Element placeholder) throws StudyUnrecognizedFormatException { - int line = getAsInt(placeholder, LINE); - int start = getAsInt(placeholder, START); - int offset = document.getLineStartOffset(line) + start; - addChildWithName(placeholder, OFFSET, offset); - } - - public static int getAsInt(Element element, String name) throws StudyUnrecognizedFormatException { - return Integer.valueOf(getChildWithName(element, name).getAttributeValue(VALUE)); - } - - public static void incrementIndex(Element element) throws StudyUnrecognizedFormatException { - Element index = getChildWithName(element, INDEX); - int indexValue = Integer.parseInt(index.getAttributeValue(VALUE)); - changeValue(index, indexValue + 1); - } - - public static void renameElement(Element element, String newName) { - element.setAttribute(NAME, newName); - } - - public static void changeValue(Element element, Object newValue) { - element.setAttribute(VALUE, newValue.toString()); - } - - public static Element addChildWithName(Element parent, String name, Element value) { - Element child = new Element(OPTION); - child.setAttribute(NAME, name); - child.addContent(value); - parent.addContent(child); - return value; - } - - public static Element addChildWithName(Element parent, String name, Object value) { - Element child = new Element(OPTION); - child.setAttribute(NAME, name); - child.setAttribute(VALUE, value.toString()); - parent.addContent(child); - return child; - } - - public static Element addChildList(Element parent, String name, List elements) { - Element listElement = new Element(LIST); - for (Element element : elements) { - listElement.addContent(element); - } - return addChildWithName(parent, name, listElement); - } - - public static Element addChildMap(Element parent, String name, Map value) { - Element mapElement = new Element(MAP); - for (Map.Entry entry : value.entrySet()) { - Element entryElement = new Element("entry"); - mapElement.addContent(entryElement); - String key = entry.getKey(); - entryElement.setAttribute("key", key); - Element valueElement = new Element("value"); - valueElement.addContent(entry.getValue()); - entryElement.addContent(valueElement); - } - return addChildWithName(parent, name, mapElement); - } - - public static Element addTextChildMap(Element parent, String name, Map value) { - Element mapElement = new Element(MAP); - for (Map.Entry entry : value.entrySet()) { - Element entryElement = new Element("entry"); - mapElement.addContent(entryElement); - String key = entry.getKey(); - entryElement.setAttribute("key", key); - entryElement.setAttribute("value", entry.getValue()); - } - return addChildWithName(parent, name, mapElement); - } - - public static List getChildList(Element parent, String name) throws StudyUnrecognizedFormatException { - return getChildList(parent, name, false); - } - - public static List getChildList(Element parent, String name, boolean optional) throws StudyUnrecognizedFormatException { - Element listParent = getChildWithName(parent, name, optional); - if (listParent != null) { - Element list = listParent.getChild(LIST); - if (list != null) { - return list.getChildren(); - } - } - return Collections.emptyList(); - } - - public static Element getChildWithName(Element parent, String name) throws StudyUnrecognizedFormatException { - return getChildWithName(parent, name, false); - } - - public static Element getChildWithName(Element parent, String name, boolean optional) throws StudyUnrecognizedFormatException { - for (Element child : parent.getChildren()) { - Attribute attribute = child.getAttribute(NAME); - if (attribute == null) { - continue; - } - if (name.equals(attribute.getValue())) { - return child; - } - } - if (optional) { - return null; - } - throw new StudyUnrecognizedFormatException(); - } - - public static Map getChildMap(Element element, String name) throws StudyUnrecognizedFormatException { - return getChildMap(element, name, false); - } - - public static Map getChildMap(Element element, String name, boolean optional) throws StudyUnrecognizedFormatException { - Element mapParent = getChildWithName(element, name, optional); - if (mapParent != null) { - Element map = mapParent.getChild(MAP); - if (map != null) { - HashMap result = new HashMap(); - for (Element entry : map.getChildren()) { - Object key = entry.getAttribute(KEY) == null ? entry.getChild(KEY).getChildren().get(0) : entry.getAttributeValue(KEY); - Object value = entry.getAttribute(VALUE) == null ? entry.getChild(VALUE).getChildren().get(0) : entry.getAttributeValue(VALUE); - result.put(key, value); - } - return result; - } - } - return Collections.emptyMap(); - } - - private static boolean isTaskDescriptionFile(@NotNull final String fileName) { - if (TASK_HTML.equals(fileName) || TASK_MD.equals(fileName)) { - return true; - } - String extension = FileUtilRt.getExtension(fileName); - if (!extension.equals(FileUtilRt.getExtension(TASK_HTML)) && !extension.equals(FileUtilRt.getExtension(TASK_MD))) { - return false; - } - return fileName.contains(EduNames.TASK) && fileName.contains(EduNames.SUBTASK_MARKER); - } - } - - public static class Json { - - public static final String TASK_LIST = "task_list"; - public static final String TASK_FILES = "task_files"; - public static final String FILES = "files"; - public static final String TESTS = "test"; - public static final String TEXTS = "text"; - public static final String HINTS = "hints"; - public static final String SUBTASK_INFOS = "subtask_infos"; - public static final String FORMAT_VERSION = "format_version"; - public static final String INDEX = "index"; - public static final String TASK_TYPE = "task_type"; - public static final String NAME = "name"; - public static final String LAST_SUBTASK = "last_subtask_index"; - - private Json() { - } - - public static class StepicStepOptionsAdapter implements JsonDeserializer { - @Override - public StepicWrappers.StepOptions deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - JsonObject stepOptionsJson = json.getAsJsonObject(); - JsonPrimitive versionJson = stepOptionsJson.getAsJsonPrimitive(FORMAT_VERSION); - int version = 1; - if (versionJson != null) { - version = versionJson.getAsInt(); - } - switch (version) { - case 1: - stepOptionsJson = convertToSecondVersion(stepOptionsJson); - case 2: - stepOptionsJson = convertToThirdVersion(stepOptionsJson); - // uncomment for future versions - //case 3: - // stepOptionsJson = convertToFourthVersion(stepOptionsJson); - } - convertSubtaskInfosToMap(stepOptionsJson); - StepicWrappers.StepOptions stepOptions = - new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create() - .fromJson(stepOptionsJson, StepicWrappers.StepOptions.class); - stepOptions.formatVersion = EduStepicConnector.CURRENT_VERSION; - return stepOptions; - } - - private static JsonObject convertToThirdVersion(JsonObject stepOptionsJson) { - if (!stepOptionsJson.has(LAST_SUBTASK)) return stepOptionsJson; - final int lastSubtaskIndex = stepOptionsJson.get(LAST_SUBTASK).getAsInt(); - if (lastSubtaskIndex == 0) return stepOptionsJson; - final JsonArray tests = stepOptionsJson.getAsJsonArray(TESTS); - if (tests.size() > 0) { - final JsonObject fileWrapper = tests.get(0).getAsJsonObject(); - if (fileWrapper.has(NAME)) { - replaceWithSubtask(fileWrapper); - } - } - final JsonArray descriptions = stepOptionsJson.getAsJsonArray(TEXTS); - if (descriptions != null && descriptions.size() > 0) { - final JsonObject fileWrapper = descriptions.get(0).getAsJsonObject(); - if (fileWrapper.has(NAME)) { - replaceWithSubtask(fileWrapper); - } - } - return stepOptionsJson; - } - - private static void replaceWithSubtask(JsonObject fileWrapper) { - final String file = fileWrapper.get(NAME).getAsString(); - final String extension = FileUtilRt.getExtension(file); - final String name = FileUtil.getNameWithoutExtension(file); - if (!name.contains(EduNames.SUBTASK_MARKER)) { - fileWrapper.remove(NAME); - fileWrapper.add(NAME, new JsonPrimitive(name + "_subtask0." + extension)); - } - } - - private static JsonObject convertSubtaskInfosToMap(JsonObject stepOptionsJson) { - final JsonArray files = stepOptionsJson.getAsJsonArray(FILES); - if (files != null) { - for (JsonElement taskFileElement : files) { - JsonObject taskFileObject = taskFileElement.getAsJsonObject(); - JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS); - for (JsonElement placeholder : placeholders) { - JsonObject placeholderObject = placeholder.getAsJsonObject(); - removeIndexFromSubtaskInfos(placeholderObject); - } - } - } - return stepOptionsJson; - } - - private static JsonObject convertToSecondVersion(JsonObject stepOptionsJson) { - Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - final JsonArray files = stepOptionsJson.getAsJsonArray(FILES); - if (files != null) { - for (JsonElement taskFileElement : files) { - JsonObject taskFileObject = taskFileElement.getAsJsonObject(); - JsonArray placeholders = taskFileObject.getAsJsonArray(PLACEHOLDERS); - for (JsonElement placeholder : placeholders) { - JsonObject placeholderObject = placeholder.getAsJsonObject(); - convertToAbsoluteOffset(taskFileObject, placeholderObject); - convertMultipleHints(gson, placeholderObject); - convertToSubtaskInfo(placeholderObject); - } - } - } - return stepOptionsJson; - } - - private static void convertMultipleHints(Gson gson, JsonObject placeholderObject) { - final String hintString = placeholderObject.getAsJsonPrimitive(HINT).getAsString(); - final JsonArray hintsArray = new JsonArray(); - - try { - final Type listType = new TypeToken>() { - }.getType(); - final List hints = gson.fromJson(hintString, listType); - if (hints != null && !hints.isEmpty()) { - for (int i = 0; i < hints.size(); i++) { - if (i == 0) { - placeholderObject.addProperty(HINT, hints.get(0)); - continue; - } - hintsArray.add(hints.get(i)); - } - placeholderObject.add(ADDITIONAL_HINTS, hintsArray); - } - else { - placeholderObject.addProperty(HINT, ""); - } - } - catch (JsonParseException e) { - hintsArray.add(hintString); - } - } - - private static void convertToAbsoluteOffset(JsonObject taskFileObject, JsonObject placeholderObject) { - int line = placeholderObject.getAsJsonPrimitive(LINE).getAsInt(); - int start = placeholderObject.getAsJsonPrimitive(START).getAsInt(); - if (line == -1) { - placeholderObject.addProperty(OFFSET, start); - } - else { - Document document = EditorFactory.getInstance().createDocument(taskFileObject.getAsJsonPrimitive(TEXT).getAsString()); - placeholderObject.addProperty(OFFSET, document.getLineStartOffset(line) + start); - } - } - } - - public static void removeIndexFromSubtaskInfos(JsonObject placeholderObject) { - JsonArray infos = placeholderObject.getAsJsonArray(SUBTASK_INFOS); - Map objectsToInsert = new HashMap<>(); - for (JsonElement info : infos) { - JsonObject object = info.getAsJsonObject(); - int index = object.getAsJsonPrimitive(INDEX).getAsInt(); - objectsToInsert.put(index, object); - } - placeholderObject.remove(SUBTASK_INFOS); - JsonObject newInfos = new JsonObject(); - placeholderObject.add(SUBTASK_INFOS, newInfos); - for (Map.Entry entry : objectsToInsert.entrySet()) { - newInfos.add(entry.getKey().toString(), entry.getValue()); - } - } - - private static void convertToSubtaskInfo(JsonObject placeholderObject) { - JsonArray subtaskInfos = new JsonArray(); - placeholderObject.add(SUBTASK_INFOS, subtaskInfos); - JsonArray hintsArray = new JsonArray(); - hintsArray.add(placeholderObject.getAsJsonPrimitive(HINT).getAsString()); - JsonArray additionalHints = placeholderObject.getAsJsonArray(ADDITIONAL_HINTS); - if (additionalHints != null) { - hintsArray.addAll(additionalHints); - } - JsonObject subtaskInfo = new JsonObject(); - subtaskInfos.add(subtaskInfo); - subtaskInfo.add(INDEX, new JsonPrimitive(0)); - subtaskInfo.add(HINTS, hintsArray); - subtaskInfo.addProperty(POSSIBLE_ANSWER, placeholderObject.getAsJsonPrimitive(POSSIBLE_ANSWER).getAsString()); - } - - public static class TaskAdapter implements JsonSerializer, JsonDeserializer { - - @Override - public JsonElement serialize(Task src, Type typeOfSrc, JsonSerializationContext context) { - Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); - JsonElement tree = gson.toJsonTree(src); - final JsonObject task = tree.getAsJsonObject(); - task.add(TASK_TYPE, new JsonPrimitive(src.getTaskType())); - return task; - } - - @Override - public Task deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); - final JsonObject object = json.getAsJsonObject(); - if (object.has(TASK_TYPE)) { - final String taskType = object.get(TASK_TYPE).getAsString(); - switch (taskType) { - case "choice": return gson.fromJson(json, ChoiceTask.class); - case "theory": return gson.fromJson(json, TheoryTask.class); - case "code": return gson.fromJson(json, CodeTask.class); - case "pycharm": return gson.fromJson(json, PyCharmTask.class); - case "subtasks": return gson.fromJson(json, TaskWithSubtasks.class); - case "output": return gson.fromJson(json, OutputTask.class); - default: { - LOG.warn("Unsupported task type " + taskType); - return null; - } - } - } - LOG.warn("No task type found in json " + json.toString()); - return null; - } - } - - public static class StepicAnswerPlaceholderAdapter implements JsonSerializer { - @Override - public JsonElement serialize(AnswerPlaceholder placeholder, Type typeOfSrc, JsonSerializationContext context) { - Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); - JsonElement answerPlaceholderJson = gson.toJsonTree(placeholder); - JsonObject answerPlaceholderObject = answerPlaceholderJson.getAsJsonObject(); - JsonObject subtaskInfos = answerPlaceholderObject.getAsJsonObject(SUBTASK_INFOS); - JsonArray infosArray = new JsonArray(); - for (Map.Entry entry : subtaskInfos.entrySet()) { - JsonObject subtaskInfo = entry.getValue().getAsJsonObject(); - subtaskInfo.add(INDEX, new JsonPrimitive(Integer.valueOf(entry.getKey()))); - infosArray.add(subtaskInfo); - } - answerPlaceholderObject.remove(SUBTASK_INFOS); - answerPlaceholderObject.add(SUBTASK_INFOS, infosArray); - return answerPlaceholderJson; - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudySettings.java b/python/educational-core/src/com/jetbrains/edu/learning/StudySettings.java deleted file mode 100644 index 637ed5d4088d..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudySettings.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.openapi.application.ApplicationManager; -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.util.messages.Topic; -import com.intellij.util.xmlb.XmlSerializerUtil; -import com.jetbrains.edu.learning.stepic.StepicUser; -import com.jetbrains.edu.learning.ui.StudyStepicUserWidget; -import org.jetbrains.annotations.Nullable; - -@State(name = "StepicUpdateSettings", storages = @Storage("other.xml")) -public class StudySettings implements PersistentStateComponent { - public static final Topic SETTINGS_CHANGED = Topic.create("Edu.UserSet", StudySettingsListener.class); - private StepicUser myUser; - public long LAST_TIME_CHECKED = 0; - private boolean myEnableTestingFromSamples = false; - public boolean myShouldUseJavaFx = StudyUtils.hasJavaFx(); - - public StudySettings() { - } - - public long getLastTimeChecked() { - return LAST_TIME_CHECKED; - } - - public void setLastTimeChecked(long timeChecked) { - LAST_TIME_CHECKED = timeChecked; - } - - @Nullable - @Override - public StudySettings getState() { - return this; - } - - @Override - public void loadState(StudySettings state) { - XmlSerializerUtil.copyBean(state, this); - } - - public static StudySettings getInstance() { - return ServiceManager.getService(StudySettings.class); - } - - @Nullable - public StepicUser getUser() { - return myUser; - } - - public void setUser(@Nullable final StepicUser user) { - myUser = user; - ApplicationManager.getApplication().getMessageBus().syncPublisher(SETTINGS_CHANGED).settingsChanged(); - updateStepicUserWidget(); - } - - public boolean shouldUseJavaFx() { - return myShouldUseJavaFx; - } - - public void setShouldUseJavaFx(boolean shouldUseJavaFx) { - this.myShouldUseJavaFx = shouldUseJavaFx; - } - - private static void updateStepicUserWidget() { - StudyStepicUserWidget widget = StudyUtils.getStepicWidget(); - if (widget != null) { - widget.update(); - } - } - - public boolean isEnableTestingFromSamples() { - return myEnableTestingFromSamples; - } - - public void setEnableTestingFromSamples(boolean enableTestingFromSamples) { - myEnableTestingFromSamples = enableTestingFromSamples; - ApplicationManager.getApplication().getMessageBus().syncPublisher(SETTINGS_CHANGED).settingsChanged(); - } - - @FunctionalInterface - public interface StudySettingsListener { - void settingsChanged(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyState.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyState.java deleted file mode 100644 index dbce0735d560..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyState.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.editor.StudyEditor; -import org.jetbrains.annotations.Nullable; - -public class StudyState { - private final StudyEditor myStudyEditor; - private final Editor myEditor; - private final TaskFile myTaskFile; - private final VirtualFile myVirtualFile; - private final Task myTask; - private final VirtualFile myTaskDir; - - public StudyState(@Nullable final StudyEditor studyEditor) { - myStudyEditor = studyEditor; - myEditor = studyEditor != null ? studyEditor.getEditor() : null; - myTaskFile = studyEditor != null ? studyEditor.getTaskFile() : null; - myVirtualFile = myEditor != null ? FileDocumentManager.getInstance().getFile(myEditor.getDocument()) : null; - myTaskDir = myVirtualFile != null ? StudyUtils.getTaskDir(myVirtualFile) : null; - myTask = myTaskFile != null ? myTaskFile.getTask() : null; - } - - public Editor getEditor() { - return myEditor; - } - - public TaskFile getTaskFile() { - return myTaskFile; - } - - public VirtualFile getVirtualFile() { - return myVirtualFile; - } - - public Task getTask() { - return myTask; - } - - public VirtualFile getTaskDir() { - return myTaskDir; - } - - public boolean isValid() { - return myStudyEditor != null && myEditor != null && - myTaskFile != null && myVirtualFile != null && - myTask != null && myTaskDir != null; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudySubtaskUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/StudySubtaskUtils.java deleted file mode 100644 index afcc0d45c5c2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudySubtaskUtils.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.undo.DocumentReferenceManager; -import com.intellij.openapi.command.undo.UndoManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.problems.WolfTheProblemSolver; -import com.intellij.ui.EditorNotifications; -import com.intellij.util.ArrayUtil; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.checker.StudyCheckUtils; -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.AnswerPlaceholderSubtaskInfo; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import com.jetbrains.edu.learning.ui.StudyToolWindow; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.util.Map; - -public class StudySubtaskUtils { - private static final Logger LOG = Logger.getInstance(StudySubtaskUtils.class); - - private StudySubtaskUtils() { - } - - public static void switchStep(@NotNull Project project, @NotNull TaskWithSubtasks task, int toSubtaskIndex) { - switchStep(project, task, toSubtaskIndex, true); - } - - /*** - * @param toSubtaskIndex from 0 to subtaskNum - 1 - */ - public static void switchStep(@NotNull Project project, @NotNull TaskWithSubtasks task, int toSubtaskIndex, boolean navigateToTask) { - if (toSubtaskIndex == task.getActiveSubtaskIndex()) { - return; - } - VirtualFile taskDir = task.getTaskDir(project); - if (taskDir == null) { - return; - } - VirtualFile srcDir = taskDir.findChild(EduNames.SRC); - if (srcDir != null) { - taskDir = srcDir; - } - int fromSubtaskIndex = task.getActiveSubtaskIndex(); - for (Map.Entry entry : task.getTaskFiles().entrySet()) { - String name = entry.getKey(); - VirtualFile virtualFile = taskDir.findFileByRelativePath(name); - if (virtualFile == null) { - continue; - } - TaskFile taskFile = entry.getValue(); - Document document = FileDocumentManager.getInstance().getDocument(virtualFile); - if (document == null) { - continue; - } - EduDocumentListener listener = null; - if (!FileEditorManager.getInstance(project).isFileOpen(virtualFile)) { - listener = new EduDocumentListener(taskFile, true); - document.addDocumentListener(listener); - } - updatePlaceholderTexts(document, taskFile, fromSubtaskIndex, toSubtaskIndex); - if (listener != null) { - document.removeDocumentListener(listener); - } - UndoManager.getInstance(project).nonundoableActionPerformed(DocumentReferenceManager.getInstance().create(document), false); - EditorNotifications.getInstance(project).updateNotifications(virtualFile); - if (StudyUtils.isStudentProject(project)) { - WolfTheProblemSolver.getInstance(project).clearProblems(virtualFile); - taskFile.setHighlightErrors(false); - } - } - transformTestFile(project, toSubtaskIndex, taskDir); - task.setActiveSubtaskIndex(toSubtaskIndex); - updateUI(project, task, !CCUtils.isCourseCreator(project) && navigateToTask); - if (CCUtils.isCourseCreator(project)) { - updateOpenedTestFiles(project, taskDir, fromSubtaskIndex, toSubtaskIndex); - } - } - - private static void updateOpenedTestFiles(@NotNull Project project, - @NotNull VirtualFile taskDir, - int fromTaskNumber, - int toSubtaskNumber) { - String fromSubtaskTestName = getTestFileName(project, fromTaskNumber); - String toSubtaskTestName = getTestFileName(project, toSubtaskNumber); - if (fromSubtaskTestName == null || toSubtaskTestName == null) { - return; - } - VirtualFile fromTest = taskDir.findChild(fromSubtaskTestName); - VirtualFile toTest = taskDir.findChild(toSubtaskTestName); - if (fromTest == null || toTest == null) { - return; - } - FileEditorManager editorManager = FileEditorManager.getInstance(project); - if (editorManager.isFileOpen(fromTest)) { - VirtualFile[] selectedFiles = editorManager.getSelectedFiles(); - boolean isSelected = ArrayUtil.contains(fromTest, selectedFiles); - editorManager.closeFile(fromTest); - editorManager.openFile(toTest, isSelected); - if (!isSelected) { - for (VirtualFile file : selectedFiles) { - editorManager.openFile(file, true); - } - } - } - } - - private static void transformTestFile(@NotNull Project project, int toSubtaskIndex, VirtualFile taskDir) { - - String subtaskTestFileName = getTestFileName(project, toSubtaskIndex); - if (subtaskTestFileName == null) { - return; - } - String nameWithoutExtension = FileUtil.getNameWithoutExtension(subtaskTestFileName); - String extension = FileUtilRt.getExtension(subtaskTestFileName); - VirtualFile subtaskTestFile = taskDir.findChild(nameWithoutExtension + ".txt"); - if (subtaskTestFile != null) { - ApplicationManager.getApplication().runWriteAction(() -> { - try { - subtaskTestFile.rename(project, nameWithoutExtension + "." + extension); - } - catch (IOException e) { - LOG.error(e); - } - }); - } - } - - @Nullable - private static String getTestFileName(@NotNull Project project, int subtaskIndex) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return null; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator == null) { - return null; - } - String defaultTestFileName = configurator.getTestFileName(); - String nameWithoutExtension = FileUtil.getNameWithoutExtension(defaultTestFileName); - String extension = FileUtilRt.getExtension(defaultTestFileName); - return nameWithoutExtension + EduNames.SUBTASK_MARKER + subtaskIndex + "." + extension; - } - - public static void updateUI(@NotNull Project project, @NotNull Task task, boolean navigateToTask) { - StudyCheckUtils.drawAllPlaceholders(project, task); - ProjectView.getInstance(project).refresh(); - StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project); - if (toolWindow != null) { - String text = task.getTaskDescription(); - if (text == null) { - task.addTaskText(task.getTaskDescriptionName(), CCUtils.TASK_DESCRIPTION_TEXT); - text = CCUtils.TASK_DESCRIPTION_TEXT; - } - toolWindow.setTaskText(text, project); - } - if (navigateToTask) { - StudyNavigator.navigateToTask(project, task); - } - } - - private static void updatePlaceholderTexts(@NotNull Document document, - @NotNull TaskFile taskFile, - int fromSubtaskIndex, - int toSubtaskIndex) { - taskFile.setTrackLengths(false); - for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { - placeholder.switchSubtask(document, fromSubtaskIndex, toSubtaskIndex); - } - taskFile.setTrackLengths(true); - } - - public static void refreshPlaceholder(@NotNull Editor editor, @NotNull AnswerPlaceholder placeholder) { - int prevSubtaskIndex = placeholder.getActiveSubtaskIndex() - 1; - AnswerPlaceholderSubtaskInfo info = placeholder.getSubtaskInfos().get(prevSubtaskIndex); - String replacementText = info != null ? info.getAnswer() : placeholder.getTaskText(); - EduUtils.replaceAnswerPlaceholder(editor.getDocument(), placeholder, placeholder.getRealLength(), replacementText); - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyTabTitleProvider.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyTabTitleProvider.java deleted file mode 100644 index 73f5e23a7b93..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyTabTitleProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider; -import com.intellij.openapi.fileEditor.impl.UniqueNameEditorTabTitleProvider; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.regex.Pattern; - -public class StudyTabTitleProvider implements EditorTabTitleProvider { - @Nullable - @Override - public String getEditorTabTitle(Project project, VirtualFile file) { - TaskFile taskFile = StudyUtils.getTaskFile(project, file); - if (taskFile == null) { - return null; - } - String title = new UniqueNameEditorTabTitleProvider().getEditorTabTitle(project, file); - if (title == null) { - return null; - } - String[] split = title.split(Pattern.quote(File.separator)); - for (int i = 0; i < split.length; i++) { - String part = split[i]; - Task task = taskFile.getTask(); - VirtualFile taskDir = task.getTaskDir(project); - if (taskDir != null && part.equals(taskDir.getName())) { - split[i] = task.getName(); - continue; - } - Lesson lesson = task.getLesson(); - int lessonIndex = lesson.getIndex(); - if (part.equals(EduNames.LESSON + lessonIndex)) { - split[i] = lesson.getName(); - } - } - return StringUtil.join(split, File.separator); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java deleted file mode 100644 index 810c24113f9f..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyTaskManager.java +++ /dev/null @@ -1,249 +0,0 @@ -package com.jetbrains.edu.learning; - -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.components.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiManager; -import com.intellij.ui.JBColor; -import com.intellij.util.containers.hash.HashMap; -import com.intellij.util.xmlb.XmlSerializer; -import com.intellij.util.xmlb.annotations.Transient; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.*; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.ui.StudyToolWindow; -import org.jdom.Element; -import org.jdom.output.XMLOutputter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static com.jetbrains.edu.learning.StudySerializationUtils.Xml.REMOTE_COURSE; - -/** - * Implementation of class which contains all the information - * about study in context of current project - */ - -@State(name = "StudySettings", storages = @Storage(value = "study_project.xml", roamingType = RoamingType.DISABLED)) -public class StudyTaskManager implements PersistentStateComponent, DumbAware { - private static final Logger LOG = Logger.getInstance(StudyTaskManager.class); - public static final int CURRENT_VERSION = 6; - private Course myCourse; - public int VERSION = CURRENT_VERSION; - - public final Map> myUserTests = new HashMap<>(); - - private StudyToolWindow.StudyToolWindowMode myToolWindowMode = StudyToolWindow.StudyToolWindowMode.TEXT; - private boolean myTurnEditingMode = false; - - @Transient private final Project myProject; - - public StudyTaskManager(Project project) { - myProject = project; - } - - public StudyTaskManager() { - this(null); - } - - public void setCourse(Course course) { - myCourse = course; - } - - @Nullable - public Course getCourse() { - return myCourse; - } - - public void setStatus(AnswerPlaceholder placeholder, StudyStatus status) { - placeholder.setStatus(status); - } - - public void addUserTest(@NotNull final Task task, UserTest userTest) { - List userTests = myUserTests.get(task); - if (userTests == null) { - userTests = new ArrayList<>(); - myUserTests.put(task, userTests); - } - userTests.add(userTest); - } - - public void setUserTests(@NotNull final Task task, @NotNull final List userTests) { - myUserTests.put(task, userTests); - } - - @NotNull - public List getUserTests(@NotNull final Task task) { - final List userTests = myUserTests.get(task); - return userTests != null ? userTests : Collections.emptyList(); - } - - public void removeUserTest(@NotNull final Task task, @NotNull final UserTest userTest) { - final List userTests = myUserTests.get(task); - if (userTests != null) { - userTests.remove(userTest); - } - } - - public JBColor getColor(@NotNull final AnswerPlaceholder placeholder) { - if (!placeholder.getUseLength() && placeholder.isActive() && placeholder.getActiveSubtaskInfo().isNeedInsertText()) { - return JBColor.LIGHT_GRAY; - } - final StudyStatus status = placeholder.getStatus(); - if (status == StudyStatus.Solved) { - return JBColor.GREEN; - } - if (status == StudyStatus.Failed) { - return JBColor.RED; - } - return JBColor.BLUE; - } - - public boolean hasFailedAnswerPlaceholders(@NotNull final TaskFile taskFile) { - return taskFile.getActivePlaceholders().size() > 0 && taskFile.hasFailedPlaceholders(); - } - - @Nullable - @Override - public Element getState() { - if (myCourse == null) { - return null; - } - - return serialize(); - } - - @NotNull - private Element serialize() { - Element el = new Element("taskManager"); - Element taskManagerElement = new Element(StudySerializationUtils.Xml.MAIN_ELEMENT); - XmlSerializer.serializeInto(this, taskManagerElement); - - if (myCourse instanceof RemoteCourse) { - try { - Element course = new Element(REMOTE_COURSE); - //noinspection RedundantCast - XmlSerializer.serializeInto((RemoteCourse)myCourse, course); - - final Element xmlCourse = StudySerializationUtils.Xml.getChildWithName(taskManagerElement, StudySerializationUtils.COURSE); - xmlCourse.removeContent(); - xmlCourse.addContent(course); - } - catch (StudySerializationUtils.StudyUnrecognizedFormatException e) { - LOG.error("Failed to serialize remote course"); - } - } - - el.addContent(taskManagerElement); - return el; - } - - @Override - public void loadState(Element state) { - try { - int version = StudySerializationUtils.Xml.getVersion(state); - if (version == -1) { - LOG.error("StudyTaskManager doesn't contain any version:\n" + state.getValue()); - return; - } - switch (version) { - case 1: - state = StudySerializationUtils.Xml.convertToSecondVersion(state); - case 2: - state = StudySerializationUtils.Xml.convertToThirdVersion(state, myProject); - case 3: - state = StudySerializationUtils.Xml.convertToForthVersion(state); - case 4: - state = StudySerializationUtils.Xml.convertToFifthVersion(state); - updateTestHelper(); - case 5: - state = StudySerializationUtils.Xml.convertToSixthVersion(state, myProject); - //uncomment for future versions - //case 6: - // state = StudySerializationUtils.Xml.convertToSixthVersion(state, myProject); - } - deserialize(state); - VERSION = CURRENT_VERSION; - if (myCourse != null) { - myCourse.initCourse(true); - } - } - catch (StudySerializationUtils.StudyUnrecognizedFormatException e) { - LOG.error("Unexpected course format:\n", new XMLOutputter().outputString(state)); - } - } - - private void updateTestHelper() { - StartupManager.getInstance(myProject).runWhenProjectIsInitialized(() -> ApplicationManager.getApplication().runWriteAction(() -> { - final VirtualFile baseDir = myProject.getBaseDir(); - if (baseDir == null) { - return; - } - final VirtualFile testHelper = baseDir.findChild(EduNames.TEST_HELPER); - if (testHelper != null) { - StudyUtils.deleteFile(testHelper); - } - final FileTemplate template = - FileTemplateManager.getInstance(myProject).getInternalTemplate(FileUtil.getNameWithoutExtension(EduNames.TEST_HELPER)); - try { - final PsiDirectory projectDir = PsiManager.getInstance(myProject).findDirectory(baseDir); - if (projectDir != null) { - FileTemplateUtil.createFromTemplate(template, EduNames.TEST_HELPER, null, projectDir); - } - } - catch (Exception e) { - LOG.warn("Failed to create new test helper"); - } - })); - } - - private void deserialize(Element state) throws StudySerializationUtils.StudyUnrecognizedFormatException { - final Element taskManagerElement = state.getChild(StudySerializationUtils.Xml.MAIN_ELEMENT); - if (taskManagerElement == null) { - throw new StudySerializationUtils.StudyUnrecognizedFormatException(); - } - XmlSerializer.deserializeInto(this, taskManagerElement); - final Element xmlCourse = StudySerializationUtils.Xml.getChildWithName(taskManagerElement, StudySerializationUtils.COURSE); - final Element remoteCourseElement = xmlCourse.getChild(REMOTE_COURSE); - if (remoteCourseElement != null) { - final RemoteCourse remoteCourse = new RemoteCourse(); - XmlSerializer.deserializeInto(remoteCourse, remoteCourseElement); - myCourse = remoteCourse; - } - } - - public static StudyTaskManager getInstance(@NotNull final Project project) { - return ServiceManager.getService(project, StudyTaskManager.class); - } - - public StudyToolWindow.StudyToolWindowMode getToolWindowMode() { - return myToolWindowMode; - } - - public void setToolWindowMode(StudyToolWindow.StudyToolWindowMode toolWindowMode) { - myToolWindowMode = toolWindowMode; - } - - public boolean isTurnEditingMode() { - return myTurnEditingMode; - } - - public void setTurnEditingMode(boolean turnEditingMode) { - myTurnEditingMode = turnEditingMode; - } - -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyTwitterPluginConfigurator.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyTwitterPluginConfigurator.java deleted file mode 100644 index dc1932597ff7..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyTwitterPluginConfigurator.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.twitter.StudyTwitterUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * Provides twitting to plugin
- * Example can be found in - * Edu Kotlin plugin - * @see com.jetbrains.edu.learning.twitter.StudyTwitterAction - * */ -public interface StudyTwitterPluginConfigurator { - ExtensionPointName EP_NAME = ExtensionPointName.create("Edu.studyTwitterPluginConfigurator"); - - /** - * To implement tweeting you should register you app in twitter. For registered application twitter provide - * consumer key and consumer secret, that are used for authorize by OAuth. - * @return consumer key for current educational plugin - */ - @NotNull - String getConsumerKey(@NotNull final Project project); - - /** - * To implement tweeting you should register you app in twitter. For registered application twitter provide - * consumer key and consumer secret, that are used for authorize by OAuth. - * @return consumer secret for current educational plugin - */ - @NotNull String getConsumerSecret(@NotNull final Project project); - - /** - * The plugin implemented tweeting should define policy when user will be asked to tweet. - *@param statusBeforeCheck @return - */ - boolean askToTweet(@NotNull final Project project, Task solvedTask, StudyStatus statusBeforeCheck); - - /** - * Stores access token and token secret, obtained by authorizing PyCharm. - */ - void storeTwitterTokens(@NotNull final Project project, @NotNull final String accessToken, @NotNull final String tokenSecret); - - /** - * @return stored access token - */ - @NotNull String getTwitterAccessToken(@NotNull Project project); - - /** - * @return stored token secret - */ - @NotNull String getTwitterTokenSecret(@NotNull Project project); - - /** - * @return panel that will be shown to user in ask to tweet dialog. - */ - @Nullable - StudyTwitterUtils.TwitterDialogPanel getTweetDialogPanel(@NotNull Task solvedTask); - - void setAskToTweet(@NotNull Project project, boolean askToTweet); - - boolean accept(@NotNull final Project project); -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyTypeHandlerDelegate.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyTypeHandlerDelegate.java deleted file mode 100644 index eebfc025cb81..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyTypeHandlerDelegate.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.codeInsight.editorActions.TypedHandlerDelegate; -import com.intellij.codeInsight.hint.HintManager; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.ui.popup.Balloon; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.psi.PsiFile; -import com.intellij.ui.HyperlinkAdapter; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.coursecreator.actions.placeholder.CCActivatePlaceholder; -import com.jetbrains.edu.coursecreator.actions.placeholder.CCAnswerPlaceholderAction; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; - -import javax.swing.event.HyperlinkEvent; -import java.util.Collections; -import java.util.stream.Collectors; - -public class StudyTypeHandlerDelegate extends TypedHandlerDelegate { - - public static final String ACTIVATE = "activate"; - public static final String SWITCH = "switch to"; - - @Override - public Result checkAutoPopup(char charTyped, Project project, Editor editor, PsiFile file) { - return handleTyping(project, editor, file, false); - } - - @Override - public Result beforeCharTyped(char c, - Project project, - Editor editor, - PsiFile file, - FileType fileType) { - return handleTyping(project, editor, file, true); - } - - @NotNull - private static Result handleTyping(Project project, Editor editor, PsiFile file, boolean showBaloon) { - if (!StudyUtils.isStudyProject(project)) { - return Result.CONTINUE; - } - TaskFile taskFile = StudyUtils.getTaskFile(project, file.getVirtualFile()); - if (taskFile == null || !(taskFile.getTask() instanceof TaskWithSubtasks)) { - return Result.CONTINUE; - } - int offset = editor.getCaretModel().getOffset(); - if (CCUtils.isCourseCreator(project)) { - AnswerPlaceholder placeholder = StudyUtils.getAnswerPlaceholder(offset, taskFile.getAnswerPlaceholders()); - if (placeholder == null || placeholder.isActive()) { - return Result.CONTINUE; - } - if (showBaloon) { - Integer toSubtask = Collections.max(placeholder.getSubtaskInfos().keySet().stream() - .filter(k -> k < ((TaskWithSubtasks)taskFile.getTask()).getActiveSubtaskIndex()) - .collect(Collectors.toList())); - int userVisibleSubtaskNum = toSubtask + 1; - String text = String - .format("To edit this placeholder activate it or switch to subtask %d", - ACTIVATE, - SWITCH, userVisibleSubtaskNum); - MyHyperlinkAdapter listener = new MyHyperlinkAdapter(taskFile, placeholder, file, editor, project, toSubtask); - Balloon balloon = - JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, null, MessageType.WARNING.getPopupBackground(), listener) - .setHideOnLinkClick(true).createBalloon(); - balloon.show(JBPopupFactory.getInstance().guessBestPopupLocation(editor), Balloon.Position.below); - } - return Result.STOP; - } - boolean insidePlaceholder = taskFile.getAnswerPlaceholder(offset) != null; - if (!insidePlaceholder) { - HintManager.getInstance().showInformationHint(editor, "Text outside of placeholders is not editable in this task"); - } - return insidePlaceholder ? Result.CONTINUE : Result.STOP; - } - - private static void activatePlaceholder(TaskFile taskFile, AnswerPlaceholder placeholder, PsiFile file, Editor editor, Project project) { - AnAction action = ActionManager.getInstance().getAction(CCActivatePlaceholder.ACTION_ID); - if (action != null) { - CCAnswerPlaceholderAction.CCState state = - new CCAnswerPlaceholderAction.CCState(taskFile, placeholder, file, editor, project); - ((CCActivatePlaceholder)action).performAnswerPlaceholderAction(state); - } - } - - private static class MyHyperlinkAdapter extends HyperlinkAdapter { - - private final TaskFile myTaskFile; - private final AnswerPlaceholder myPlaceholder; - private final PsiFile myFile; - private final Editor myEditor; - private final Project myProject; - private final Integer myToSubtask; - - public MyHyperlinkAdapter(TaskFile taskFile, - AnswerPlaceholder placeholder, - PsiFile file, - Editor editor, - Project project, - Integer toSubtask) { - myTaskFile = taskFile; - myPlaceholder = placeholder; - myFile = file; - myEditor = editor; - myProject = project; - myToSubtask = toSubtask; - } - - @Override - protected void hyperlinkActivated(HyperlinkEvent e) { - String description = e.getDescription(); - if (ACTIVATE.equals(description)) { - activatePlaceholder(myTaskFile, myPlaceholder, - myFile, myEditor, - myProject); - } - if (SWITCH.equals(description)) { - StudySubtaskUtils.switchStep(myProject, - (TaskWithSubtasks)myTaskFile - .getTask(), - myToSubtask); - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java deleted file mode 100644 index 689dc3767758..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/StudyUtils.java +++ /dev/null @@ -1,751 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.ide.IdeView; -import com.intellij.ide.fileTemplates.FileTemplate; -import com.intellij.ide.fileTemplates.FileTemplateManager; -import com.intellij.ide.fileTemplates.FileTemplateUtil; -import com.intellij.ide.projectView.ProjectView; -import com.intellij.ide.util.EditorHelper; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.ScrollType; -import com.intellij.openapi.editor.actionSystem.EditorActionManager; -import com.intellij.openapi.editor.colors.EditorColors; -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.impl.DocumentImpl; -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.ui.MessageType; -import com.intellij.openapi.ui.popup.Balloon; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.Disposer; -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.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl; -import com.intellij.openapi.wm.*; -import com.intellij.openapi.wm.impl.IdeFrameImpl; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.ui.JBColor; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.content.Content; -import com.intellij.util.DocumentUtil; -import com.intellij.util.TimeoutUtil; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.text.MarkdownUtil; -import com.intellij.util.ui.UIUtil; -import com.jetbrains.edu.learning.core.EduAnswerPlaceholderDeleteHandler; -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.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.ChoiceTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TheoryTask; -import com.jetbrains.edu.learning.editor.StudyEditor; -import com.jetbrains.edu.learning.stepic.EduStepikUtils; -import com.jetbrains.edu.learning.stepic.OAuthDialog; -import com.jetbrains.edu.learning.stepic.StepicUser; -import com.jetbrains.edu.learning.ui.StudyStepicUserWidget; -import com.jetbrains.edu.learning.ui.StudyToolWindow; -import com.jetbrains.edu.learning.ui.StudyToolWindowFactory; -import com.petebevin.markdown.MarkdownProcessor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.io.*; -import java.util.*; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -import static com.jetbrains.edu.learning.navigation.StudyNavigator.navigateToTask; - -public class StudyUtils { - private StudyUtils() { - } - - private static final Logger LOG = Logger.getInstance(StudyUtils.class.getName()); - private static final String ourPrefix = "
"; - - private static final String ourPostfix = "
"; - - public static void closeSilently(@Nullable final Closeable stream) { - if (stream != null) { - try { - stream.close(); - } - catch (IOException e) { - // close silently - } - } - } - - public static boolean isZip(String fileName) { - return fileName.contains(".zip"); - } - - @Nullable - public static T getFirst(@NotNull final Iterable container) { - Iterator iterator = container.iterator(); - if (!iterator.hasNext()) { - return null; - } - return iterator.next(); - } - - public static boolean indexIsValid(int index, @NotNull final Collection collection) { - int size = collection.size(); - return index >= 0 && index < size; - } - - @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - @Nullable - public static String getFileText(@Nullable final String parentDir, @NotNull final String fileName, boolean wrapHTML, - @NotNull final String encoding) { - final File inputFile = parentDir != null ? new File(parentDir, fileName) : new File(fileName); - if (!inputFile.exists()) return null; - final StringBuilder taskText = new StringBuilder(); - BufferedReader reader = null; - try { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), encoding)); - String line; - while ((line = reader.readLine()) != null) { - taskText.append(line).append("\n"); - if (wrapHTML) { - taskText.append("
"); - } - } - return wrapHTML ? UIUtil.toHtml(taskText.toString()) : taskText.toString(); - } - catch (IOException e) { - LOG.info("Failed to get file text from file " + fileName, e); - } - finally { - closeSilently(reader); - } - return null; - } - - public static void updateAction(@NotNull final AnActionEvent e) { - final Presentation presentation = e.getPresentation(); - presentation.setEnabled(false); - final Project project = e.getProject(); - if (project != null) { - final StudyEditor studyEditor = getSelectedStudyEditor(project); - if (studyEditor != null) { - presentation.setEnabledAndVisible(true); - } - } - } - - public static void updateToolWindows(@NotNull final Project project) { - final StudyToolWindow studyToolWindow = getStudyToolWindow(project); - if (studyToolWindow != null) { - String taskText = getTaskText(project); - if (taskText != null) { - studyToolWindow.setTaskText(taskText, project); - } - else { - LOG.warn("Task text is null"); - } - studyToolWindow.updateCourseProgress(project); - } - } - - public static void initToolWindows(@NotNull final Project project) { - final ToolWindowManager windowManager = ToolWindowManager.getInstance(project); - windowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW).getContentManager().removeAllContents(false); - StudyToolWindowFactory factory = new StudyToolWindowFactory(); - factory.createToolWindowContent(project, windowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW)); - - } - - @Nullable - public static StudyToolWindow getStudyToolWindow(@NotNull final Project project) { - if (project.isDisposed()) return null; - - ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW); - if (toolWindow != null) { - Content[] contents = toolWindow.getContentManager().getContents(); - for (Content content: contents) { - JComponent component = content.getComponent(); - if (component != null && component instanceof StudyToolWindow) { - return (StudyToolWindow)component; - } - } - } - return null; - } - - public static void deleteFile(@Nullable final VirtualFile file) { - if (file == null) { - return; - } - try { - file.delete(StudyUtils.class); - } - catch (IOException e) { - LOG.error(e); - } - } - - - /** - * shows pop up in the center of "check task" button in study editor - */ - public static void showCheckPopUp(@NotNull final Project project, @NotNull final Balloon balloon) { - final StudyEditor studyEditor = getSelectedStudyEditor(project); - assert studyEditor != null; - - balloon.show(computeLocation(studyEditor.getEditor()), Balloon.Position.above); - Disposer.register(project, balloon); - } - - public static RelativePoint computeLocation(Editor editor){ - - final Rectangle visibleRect = editor.getComponent().getVisibleRect(); - Point point = new Point(visibleRect.x + visibleRect.width + 10, - visibleRect.y + 10); - return new RelativePoint(editor.getComponent(), point); - } - - - public static boolean isTestsFile(@NotNull Project project, @NotNull final String name) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return false; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator == null) { - return false; - } - String testFileName = configurator.getTestFileName(); - return name.equals(testFileName) || - name.startsWith(FileUtil.getNameWithoutExtension(testFileName)) && name.contains(EduNames.SUBTASK_MARKER); - } - - @Nullable - public static TaskFile getTaskFile(@NotNull final Project project, @NotNull final VirtualFile file) { - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return null; - } - VirtualFile taskDir = getTaskDir(file); - if (taskDir == null) { - return null; - } - //need this because of multi-module generation - if (EduNames.SRC.equals(taskDir.getName())) { - taskDir = taskDir.getParent(); - if (taskDir == null) { - return null; - } - } - final String taskDirName = taskDir.getName(); - if (taskDirName.contains(EduNames.TASK)) { - final VirtualFile lessonDir = taskDir.getParent(); - if (lessonDir != null) { - int lessonIndex = EduUtils.getIndex(lessonDir.getName(), EduNames.LESSON); - List lessons = course.getLessons(); - if (!indexIsValid(lessonIndex, lessons)) { - return null; - } - final Lesson lesson = lessons.get(lessonIndex); - int taskIndex = EduUtils.getIndex(taskDirName, EduNames.TASK); - final List tasks = lesson.getTaskList(); - if (!indexIsValid(taskIndex, tasks)) { - return null; - } - final Task task = tasks.get(taskIndex); - return task.getFile(pathRelativeToTask(file)); - } - } - return null; - } - - public static void drawAllAnswerPlaceholders(Editor editor, TaskFile taskFile) { - editor.getMarkupModel().removeAllHighlighters(); - final Project project = editor.getProject(); - if (project == null) return; - final StudyTaskManager taskManager = StudyTaskManager.getInstance(project); - for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { - final JBColor color = taskManager.getColor(answerPlaceholder); - EduAnswerPlaceholderPainter.drawAnswerPlaceholder(editor, answerPlaceholder, color); - } - - final Document document = editor.getDocument(); - EditorActionManager.getInstance() - .setReadonlyFragmentModificationHandler(document, new EduAnswerPlaceholderDeleteHandler(editor)); - EduAnswerPlaceholderPainter.createGuardedBlocks(editor, taskFile); - editor.getColorsScheme().setColor(EditorColors.READONLY_FRAGMENT_BACKGROUND_COLOR, null); - } - - @Nullable - public static StudyEditor getSelectedStudyEditor(@NotNull final Project project) { - try { - final FileEditor fileEditor = FileEditorManagerEx.getInstanceEx(project).getSplitters().getCurrentWindow(). - getSelectedEditor().getSelectedEditorWithProvider().getFirst(); - if (fileEditor instanceof StudyEditor) { - return (StudyEditor)fileEditor; - } - } - catch (Exception e) { - return null; - } - return null; - } - - @Nullable - public static Editor getSelectedEditor(@NotNull final Project project) { - final StudyEditor studyEditor = getSelectedStudyEditor(project); - if (studyEditor != null) { - return studyEditor.getEditor(); - } - return null; - } - - public static void deleteGuardedBlocks(@NotNull final Document document) { - if (document instanceof DocumentImpl) { - final DocumentImpl documentImpl = (DocumentImpl)document; - List blocks = documentImpl.getGuardedBlocks(); - for (final RangeMarker block : blocks) { - ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> document.removeGuardedBlock(block))); - } - } - } - - public static boolean isRenameableOrMoveable(@NotNull final Project project, @NotNull final Course course, @NotNull final PsiElement element) { - if (element instanceof PsiFile) { - VirtualFile virtualFile = ((PsiFile)element).getVirtualFile(); - if (project.getBaseDir().equals(virtualFile.getParent())) { - return false; - } - TaskFile file = getTaskFile(project, virtualFile); - if (file != null) { - return false; - } - String name = virtualFile.getName(); - return !isTestsFile(project, name); - } - if (element instanceof PsiDirectory) { - VirtualFile virtualFile = ((PsiDirectory)element).getVirtualFile(); - VirtualFile parent = virtualFile.getParent(); - if (parent == null) { - return true; - } - if (project.getBaseDir().equals(parent)) { - return false; - } - Lesson lesson = course.getLesson(parent.getName()); - if (lesson != null) { - Task task = lesson.getTask(virtualFile.getName()); - if (task != null) { - return false; - } - } - } - return true; - } - - public static boolean canRenameOrMove(DataContext dataContext) { - Project project = CommonDataKeys.PROJECT.getData(dataContext); - PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext); - if (element == null || project == null) { - return false; - } - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null || !EduNames.STUDY.equals(course.getCourseMode())) { - return false; - } - - return !isRenameableOrMoveable(project, course, element); - } - - public static String wrapTextToDisplayLatex(String taskTextFileHtml) { - final String prefix = String.format(ourPrefix, EditorColorsManager.getInstance().getGlobalScheme().getEditorFontSize()); - return prefix + taskTextFileHtml + ourPostfix; - } - - @Nullable - public static StudyTwitterPluginConfigurator getTwitterConfigurator(@NotNull final Project project) { - StudyTwitterPluginConfigurator[] extensions = StudyTwitterPluginConfigurator.EP_NAME.getExtensions(); - for (StudyTwitterPluginConfigurator extension: extensions) { - if (extension.accept(project)) { - return extension; - } - } - return null; - } - - @Nullable - public static String getTaskText(@NotNull final Project project) { - Task task = getCurrentTask(project); - if (task == null) { - return StudyToolWindow.EMPTY_TASK_TEXT; - } - return task.getTaskDescription(); - } - - @Nullable - public static TaskFile getSelectedTaskFile(@NotNull Project project) { - VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles(); - TaskFile taskFile = null; - for (VirtualFile file : files) { - taskFile = getTaskFile(project, file); - if (taskFile != null) { - break; - } - } - return taskFile; - } - - @Nullable - public static Task getCurrentTask(@NotNull final Project project) { - final TaskFile taskFile = getSelectedTaskFile(project); - return taskFile != null ? taskFile.getTask() : null; - } - - public static boolean isStudyProject(@NotNull Project project) { - return StudyTaskManager.getInstance(project).getCourse() != null; - } - - public static boolean isStudentProject(@NotNull Project project) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - return course != null && EduNames.STUDY.equals(course.getCourseMode()); - } - - public static boolean hasJavaFx() { - try { - Class.forName("javafx.application.Platform"); - return true; - } - catch (ClassNotFoundException e) { - return false; - } - } - - @Nullable - public static Task getTask(@NotNull Project project, @NotNull VirtualFile taskVF) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return null; - } - VirtualFile lessonVF = taskVF.getParent(); - if (lessonVF == null) { - return null; - } - Lesson lesson = course.getLesson(lessonVF.getName()); - if (lesson == null) { - return null; - } - return lesson.getTask(taskVF.getName()); - } - - @Nullable - public static VirtualFile getTaskDir(@NotNull VirtualFile taskFile) { - VirtualFile parent = taskFile.getParent(); - - while (parent != null) { - String name = parent.getName(); - - if (name.contains(EduNames.TASK) && parent.isDirectory()) { - return parent; - } - if (EduNames.SRC.equals(name)) { - return parent.getParent(); - } - - parent = parent.getParent(); - } - return null; - } - - @Nullable - public static Task getTaskForFile(@NotNull Project project, @NotNull VirtualFile taskFile) { - VirtualFile taskDir = getTaskDir(taskFile); - if (taskDir == null) { - return null; - } - return getTask(project, taskDir); - } - - // supposed to be called under progress - @Nullable - public static T execCancelable(@NotNull final Callable callable) { - final Future 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; - } - - public static String convertToHtml(@NotNull String content) { - ArrayList lines = ContainerUtil.newArrayList(content.split("\n|\r|\r\n")); - MarkdownUtil.replaceHeaders(lines); - MarkdownUtil.replaceCodeBlock(lines); - - return new MarkdownProcessor().markdown(StringUtil.join(lines, "\n")); - } - - @Nullable - public static Document getDocument(String basePath, int lessonIndex, int taskIndex, String fileName) { - String taskPath = FileUtil.join(basePath, EduNames.LESSON + lessonIndex, EduNames.TASK + taskIndex); - VirtualFile taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, fileName)); - if (taskFile == null) { - taskFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.join(taskPath, EduNames.SRC, fileName)); - } - if (taskFile == null) { - return null; - } - return FileDocumentManager.getInstance().getDocument(taskFile); - } - - public static void showErrorPopupOnToolbar(@NotNull Project project, String content) { - final Balloon balloon = - JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(content, MessageType.ERROR, null).createBalloon(); - showCheckPopUp(project, balloon); - } - - public static void selectFirstAnswerPlaceholder(@Nullable final StudyEditor studyEditor, @NotNull final Project project) { - if (studyEditor == null) return; - final Editor editor = studyEditor.getEditor(); - IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true); - final List placeholders = studyEditor.getTaskFile().getActivePlaceholders(); - if (placeholders.isEmpty()) return; - final AnswerPlaceholder placeholder = placeholders.get(0); - Pair offsets = getPlaceholderOffsets(placeholder, editor.getDocument()); - editor.getSelectionModel().setSelection(offsets.first, offsets.second); - editor.getCaretModel().moveToOffset(offsets.first); - editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); - } - - public static void registerStudyToolWindow(@Nullable final Course course, Project project) { - if (course != null && EduNames.PYCHARM.equals(course.getCourseType())) { - final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); - registerToolWindows(toolWindowManager, project); - final ToolWindow studyToolWindow = toolWindowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW); - if (studyToolWindow != null) { - studyToolWindow.show(null); - initToolWindows(project); - } - } - } - - private static void registerToolWindows(@NotNull final ToolWindowManager toolWindowManager, Project project) { - final ToolWindow toolWindow = toolWindowManager.getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW); - if (toolWindow == null) { - toolWindowManager.registerToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW, true, ToolWindowAnchor.RIGHT, project, true); - } - } - - @Nullable public static AnswerPlaceholder getAnswerPlaceholder(int offset, List placeholders) { - for (AnswerPlaceholder placeholder : placeholders) { - int placeholderStart = placeholder.getOffset(); - int placeholderEnd = placeholderStart + placeholder.getRealLength(); - if (placeholderStart <= offset && offset <= placeholderEnd) { - return placeholder; - } - } - return null; - } - - public static String pathRelativeToTask(VirtualFile file) { - VirtualFile taskDir = getTaskDir(file); - if (taskDir == null) return file.getName(); - VirtualFile srcDir = taskDir.findChild(EduNames.SRC); - if (srcDir != null) { - taskDir = srcDir; - } - return FileUtil.getRelativePath(taskDir.getPath(), file.getPath(), '/'); - } - - public static Pair getPlaceholderOffsets(@NotNull final AnswerPlaceholder answerPlaceholder, - @NotNull final Document document) { - int startOffset = answerPlaceholder.getOffset(); - int delta = 0; - final int length = answerPlaceholder.getRealLength(); - int nonSpaceCharOffset = DocumentUtil.getFirstNonSpaceCharOffset(document, startOffset, startOffset + length); - if (nonSpaceCharOffset != startOffset) { - delta = startOffset - nonSpaceCharOffset; - startOffset = nonSpaceCharOffset; - } - final int endOffset = startOffset + length + delta; - return Pair.create(startOffset, endOffset); - } - - public static boolean isCourseValid(@Nullable Course course) { - if (course == null) return false; - if (course.isAdaptive()) { - final List lessons = course.getLessons(); - if (lessons.size() == 1) { - return !lessons.get(0).getTaskList().isEmpty(); - } - } - return true; - } - - public static void createFromTemplate(@NotNull Project project, - @NotNull PsiDirectory taskDirectory, - @NotNull String name, - @Nullable IdeView view, - boolean open) { - FileTemplate template = FileTemplateManager.getInstance(project).getInternalTemplate(name); - if (template == null) { - LOG.info("Template " + name + " wasn't found"); - return; - } - try { - final PsiElement file = FileTemplateUtil.createFromTemplate(template, name, null, taskDirectory); - if (view != null && open) { - EditorHelper.openInEditor(file, false); - view.selectElement(file); - } - } - catch (Exception e) { - LOG.error(e); - } - } - public static void openFirstTask(@NotNull final Course course, @NotNull final Project project) { - LocalFileSystem.getInstance().refresh(false); - final Lesson firstLesson = getFirst(course.getLessons()); - if (firstLesson == null) return; - final Task firstTask = getFirst(firstLesson.getTaskList()); - if (firstTask == null) return; - final VirtualFile taskDir = firstTask.getTaskDir(project); - if (taskDir == null) return; - final Map taskFiles = firstTask.getTaskFiles(); - VirtualFile activeVirtualFile = null; - for (Map.Entry entry : taskFiles.entrySet()) { - final String relativePath = entry.getKey(); - final TaskFile taskFile = entry.getValue(); - taskDir.refresh(false, true); - final VirtualFile virtualFile = taskDir.findFileByRelativePath(relativePath); - if (virtualFile != null) { - if (!taskFile.getActivePlaceholders().isEmpty()) { - activeVirtualFile = virtualFile; - } - } - } - if (activeVirtualFile != null) { - final PsiFile file = PsiManager.getInstance(project).findFile(activeVirtualFile); - ProjectView.getInstance(project).select(file, activeVirtualFile, false); - final FileEditor[] editors = FileEditorManager.getInstance(project).openFile(activeVirtualFile, true); - if (editors.length == 0) { - return; - } - final FileEditor studyEditor = editors[0]; - if (studyEditor instanceof StudyEditor) { - selectFirstAnswerPlaceholder((StudyEditor)studyEditor, project); - } - FileEditorManager.getInstance(project).openFile(activeVirtualFile, true); - } - else { - String first = getFirst(taskFiles.keySet()); - if (first != null) { - NewVirtualFile firstFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(first); - if (firstFile != null) { - FileEditorManager.getInstance(project).openFile(firstFile, true); - } - } - } - } - - public static void navigateToStep(@NotNull Project project, @NotNull Course course, int stepId) { - if (stepId == 0 || course.isAdaptive()) { - return; - } - Task task = getTask(course, stepId); - if (task != null) { - navigateToTask(project, task); - } - } - - @Nullable - private static Task getTask(@NotNull Course course, int stepId) { - for (Lesson lesson : course.getLessons()) { - Task task = lesson.getTask(stepId); - if (task != null) { - return task; - } - } - return null; - } - - @Nullable - static StudyStepicUserWidget getStepicWidget() { - JFrame frame = WindowManager.getInstance().findVisibleFrame(); - if (frame instanceof IdeFrameImpl) { - return (StudyStepicUserWidget)((IdeFrameImpl)frame).getStatusBar().getWidget(StudyStepicUserWidget.ID); - } - return null; - } - - public static void showOAuthDialog() { - OAuthDialog dialog = new OAuthDialog(); - if (dialog.showAndGet()) { - StepicUser user = dialog.getStepicUser(); - StudySettings.getInstance().setUser(user); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyActionWithShortcut.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyActionWithShortcut.java deleted file mode 100644 index ae1fa651031c..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyActionWithShortcut.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.openapi.project.DumbAwareAction; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public abstract class StudyActionWithShortcut extends DumbAwareAction { - - public StudyActionWithShortcut(@Nullable String text, @Nullable String description, @Nullable Icon icon) { - super(text, description, icon); - } - - @NotNull - public abstract String getActionId(); - - @Nullable - public abstract String[] getShortcuts(); -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyAfterCheckAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyAfterCheckAction.java deleted file mode 100644 index c42ee467981f..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyAfterCheckAction.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; - -public abstract class StudyAfterCheckAction { - public abstract void run(@NotNull final Project project, @NotNull final Task solvedTask, StudyStatus statusBeforeCheck); -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java deleted file mode 100644 index 200e90eaa858..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.progress.PerformInBackgroundOption; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudySettings; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.checker.StudyCheckListener; -import com.jetbrains.edu.learning.checker.StudyCheckResult; -import com.jetbrains.edu.learning.checker.StudyCheckUtils; -import com.jetbrains.edu.learning.checker.StudyTaskChecker; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TheoryTask; -import com.jetbrains.edu.learning.editor.StudyEditor; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; - -public class StudyCheckAction extends StudyActionWithShortcut { - public static final String SHORTCUT = "ctrl alt pressed ENTER"; - public static final String ACTION_ID = "Edu.Check"; - private static final String TEXT = "Check Task"; - public static final String FAILED_CHECK_LAUNCH = "Failed to launch checking"; - - protected final Ref myCheckInProgress = new Ref<>(false); - - public StudyCheckAction() { - super(TEXT,"Check current task", EducationalCoreIcons.CheckTask); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - EduUsagesCollector.taskChecked(); - Project project = e.getProject(); - if (project == null) { - return; - } - if (DumbService.isDumb(project)) { - StudyCheckUtils - .showTestResultPopUp("Checking is not available while indexing is in progress", MessageType.WARNING.getPopupBackground(), project); - return; - } - StudyCheckUtils.hideTestResultsToolWindow(project); - FileDocumentManager.getInstance().saveAllDocuments(); - Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor(); - if (editor == null) { - return; - } - VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(editor.getDocument()); - if (virtualFile == null) { - return; - } - Task task = StudyUtils.getTaskForFile(project, virtualFile); - if (task == null) { - return; - } - for (StudyCheckListener listener : Extensions.getExtensions(StudyCheckListener.EP_NAME)) { - listener.beforeCheck(project, task); - } - ProgressManager.getInstance().run(new StudyCheckTask(project, task)); - } - - @Override - public void update(AnActionEvent e) { - final Presentation presentation = e.getPresentation(); - StudyUtils.updateAction(e); - if (presentation.isEnabled()) { - updateDescription(e); - presentation.setEnabled(!myCheckInProgress.get()); - } - } - - private static void updateDescription(AnActionEvent e) { - final Presentation presentation = e.getPresentation(); - final Project project = e.getProject(); - if (project != null) { - final StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - if (studyEditor != null) { - final Task task = studyEditor.getTaskFile().getTask(); - if (task instanceof TheoryTask) { - presentation.setText(task.getLesson().getCourse().isAdaptive() ? "Get Next Recommendation" : "Mark as read"); - } - else { - presentation.setText(TEXT); - } - } - } - } - - @NotNull - @Override - public String getActionId() { - return ACTION_ID; - } - - @Override - public String[] getShortcuts() { - return new String[]{SHORTCUT}; - } - - private class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable { - private final Project myProject; - private final Task myTask; - private final StudyTaskChecker myChecker; - private StudyCheckResult myResult; - - public StudyCheckTask(@NotNull Project project, @NotNull Task task) { - super(project, "Checking Task", true, PerformInBackgroundOption.DEAF); - myProject = project; - myTask = task; - myChecker = task.getChecker(myProject); - } - - @Override - public void run(@NotNull ProgressIndicator indicator) { - indicator.setIndeterminate(true); - myCheckInProgress.set(true); - boolean isRemote = myTask.getLesson().getCourse() instanceof RemoteCourse; - myResult = isRemote ? checkOnRemote() : myChecker.check(); - } - - @Override - public void onSuccess() { - String message = myResult.getMessage(); - StudyStatus status = myResult.getStatus(); - myTask.setStatus(status); - switch (status) { - case Failed: - myChecker.onTaskFailed(message); - break; - case Solved: - myChecker.onTaskSolved(message); - break; - default: - StudyCheckUtils.showTestResultPopUp(message, MessageType.WARNING.getPopupBackground(), myProject); - } - ApplicationManager.getApplication().invokeLater(() -> { - StudyUtils.updateToolWindows(myProject); - ProjectView.getInstance(myProject).refresh(); - - for (StudyCheckListener listener : StudyCheckListener.EP_NAME.getExtensions()) { - listener.afterCheck(myProject, myTask); - } - }); - myChecker.clearState(); - myCheckInProgress.set(false); - } - - @Override - public void onCancel() { - myChecker.clearState(); - myCheckInProgress.set(false); - } - - private StudyCheckResult checkOnRemote() { - return myChecker.checkOnRemote(StudySettings.getInstance().getUser()); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyEditInputAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyEditInputAction.java deleted file mode 100644 index 82de1c263193..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyEditInputAction.java +++ /dev/null @@ -1,240 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.icons.AllIcons; -import com.intellij.ide.ui.UISettings; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.ui.popup.JBPopupAdapter; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.ui.popup.LightweightWindowEvent; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.ui.tabs.TabInfo; -import com.intellij.ui.tabs.TabsListener; -import com.intellij.ui.tabs.impl.JBEditorTabs; -import com.intellij.util.PlatformIcons; -import com.jetbrains.edu.learning.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.TaskFile; -import com.jetbrains.edu.learning.courseFormat.UserTest; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.editor.StudyEditor; -import com.jetbrains.edu.learning.ui.StudyTestContentPanel; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class StudyEditInputAction extends DumbAwareAction { - - private static final Logger LOG = Logger.getInstance(StudyEditInputAction.class.getName()); - private JBEditorTabs tabbedPane; - private Map myEditableTabs = new HashMap<>(); - - public StudyEditInputAction() { - super("Watch Test Input", "Watch test input", EducationalCoreIcons.WatchInput); - } - - public void showInput(final Project project) { - final Editor selectedEditor = StudyUtils.getSelectedEditor(project); - if (selectedEditor != null) { - FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); - final VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument()); - final StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project); - assert openedFile != null; - TaskFile taskFile = StudyUtils.getTaskFile(project, openedFile); - assert taskFile != null; - final Task currentTask = taskFile.getTask(); - tabbedPane = new JBEditorTabs(project, ActionManager.getInstance(), IdeFocusManager.findInstance(), project); - tabbedPane.addListener(new TabsListener.Adapter() { - @Override - public void selectionChanged(TabInfo oldSelection, TabInfo newSelection) { - if (newSelection.getIcon() != null) { - int tabCount = tabbedPane.getTabCount(); - VirtualFile taskDir = StudyUtils.getTaskDir(openedFile); - VirtualFile testsDir = taskDir.findChild(EduNames.USER_TESTS); - assert testsDir != null; - UserTest userTest = createUserTest(testsDir, currentTask, studyTaskManager); - userTest.setEditable(true); - StudyTestContentPanel testContentPanel = new StudyTestContentPanel(userTest); - TabInfo testTab = addTestTab(tabbedPane.getTabCount(), testContentPanel, currentTask, true); - myEditableTabs.put(testTab, userTest); - tabbedPane.addTabSilently(testTab, tabCount - 1); - tabbedPane.select(testTab, true); - } - } - }); - - List userTests = studyTaskManager.getUserTests(currentTask); - int i = 1; - for (UserTest userTest : userTests) { - String inputFileText = StudyUtils.getFileText(null, userTest.getInput(), false, "UTF-8"); - String outputFileText = StudyUtils.getFileText(null, userTest.getOutput(), false, "UTF-8"); - StudyTestContentPanel myContentPanel = new StudyTestContentPanel(userTest); - myContentPanel.addInputContent(inputFileText); - myContentPanel.addOutputContent(outputFileText); - TabInfo testTab = addTestTab(i, myContentPanel, currentTask, userTest.isEditable()); - tabbedPane.addTabSilently(testTab, i - 1); - if (userTest.isEditable()) { - myEditableTabs.put(testTab, userTest); - } - i++; - } - TabInfo plusTab = new TabInfo(new JPanel()); - plusTab.setIcon(PlatformIcons.ADD_ICON); - tabbedPane.addTabSilently(plusTab, tabbedPane.getTabCount()); - final JBPopup hint = - JBPopupFactory.getInstance().createComponentPopupBuilder(tabbedPane.getComponent(), tabbedPane.getComponent()) - .setResizable(true) - .setMovable(true) - .setRequestFocus(true) - .createPopup(); - StudyEditor selectedStudyEditor = StudyUtils.getSelectedStudyEditor(project); - assert selectedStudyEditor != null; - hint.showInCenterOf(selectedStudyEditor.getComponent()); - hint.addListener(new HintClosedListener(currentTask, studyTaskManager)); - } - } - - - private static void flushBuffer(@NotNull final StringBuilder buffer, @NotNull final File file) { - PrintWriter printWriter = null; - try { - //noinspection IOResourceOpenedButNotSafelyClosed - printWriter = new PrintWriter(new FileOutputStream(file)); - printWriter.print(buffer.toString()); - } - catch (FileNotFoundException e) { - LOG.error(e); - } - finally { - StudyUtils.closeSilently(printWriter); - } - EduUtils.synchronize(); - } - - private static UserTest createUserTest(@NotNull final VirtualFile testsDir, - @NotNull final Task currentTask, - StudyTaskManager studyTaskManager) { - UserTest userTest = new UserTest(); - List userTests = studyTaskManager.getUserTests(currentTask); - int testNum = userTests.size() + 1; - String inputName = EduNames.USER_TEST_INPUT + testNum; - File inputFile = new File(testsDir.getPath(), inputName); - String outputName = EduNames.USER_TEST_OUTPUT + testNum; - File outputFile = new File(testsDir.getPath(), outputName); - userTest.setInput(inputFile.getPath()); - userTest.setOutput(outputFile.getPath()); - studyTaskManager.addUserTest(currentTask, userTest); - return userTest; - } - - private TabInfo addTestTab(int nameIndex, final StudyTestContentPanel contentPanel, @NotNull final Task currentTask, boolean toBeClosable) { - TabInfo testTab = toBeClosable ? createClosableTab(contentPanel, currentTask) : new TabInfo(contentPanel); - return testTab.setText(EduNames.TEST_TAB_NAME + String.valueOf(nameIndex)); - } - - private TabInfo createClosableTab(StudyTestContentPanel contentPanel, Task currentTask) { - TabInfo closableTab = new TabInfo(contentPanel); - final DefaultActionGroup tabActions = new DefaultActionGroup(); - tabActions.add(new CloseTab(closableTab, currentTask)); - closableTab.setTabLabelActions(tabActions, ActionPlaces.EDITOR_TAB); - return closableTab; - } - - @Override - public void actionPerformed(AnActionEvent e) { - showInput(e.getProject()); - } - - private static class HintClosedListener extends JBPopupAdapter { - private final Task myTask; - private final StudyTaskManager myStudyTaskManager; - - private HintClosedListener(@NotNull final Task task, StudyTaskManager studyTaskManager) { - myTask = task; - myStudyTaskManager = studyTaskManager; - } - - @Override - public void onClosed(LightweightWindowEvent event) { - for (final UserTest userTest : myStudyTaskManager.getUserTests(myTask)) { - ApplicationManager.getApplication().runWriteAction(() -> { - if (userTest.isEditable()) { - File inputFile = new File(userTest.getInput()); - File outputFile = new File(userTest.getOutput()); - flushBuffer(userTest.getInputBuffer(), inputFile); - flushBuffer(userTest.getOutputBuffer(), outputFile); - } - }); - } - } - } - - private class CloseTab extends AnAction implements DumbAware { - - private final TabInfo myTabInfo; - private final Task myTask; - - public CloseTab(final TabInfo info, @NotNull final Task task) { - myTabInfo = info; - myTask = task; - } - - @Override - public void update(final AnActionEvent e) { - e.getPresentation().setIcon(AllIcons.Actions.Close); - e.getPresentation().setHoveredIcon(AllIcons.Actions.CloseHovered); - e.getPresentation().setVisible(UISettings.getInstance().getShowCloseButton()); - e.getPresentation().setText("Delete test"); - } - - @Override - public void actionPerformed(final AnActionEvent e) { - tabbedPane.removeTab(myTabInfo); - UserTest userTest = myEditableTabs.get(myTabInfo); - File testInputFile = new File(userTest.getInput()); - File testOutputFile = new File(userTest.getOutput()); - if (testInputFile.delete() && testOutputFile.delete()) { - EduUtils.synchronize(); - } else { - LOG.error("failed to delete user tests"); - } - final Project project = e.getProject(); - if (project != null) { - StudyTaskManager.getInstance(project).removeUserTest(myTask, userTest); - } - } - } - @Override - public void update(final AnActionEvent e) { - EduUtils.enableAction(e, false); - - final Project project = e.getProject(); - if (project != null) { - StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - if (studyEditor != null) { - final List userTests = StudyTaskManager.getInstance(project).getUserTests(studyEditor.getTaskFile().getTask()); - if (!userTests.isEmpty()) { - EduUtils.enableAction(e, true); - } - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java deleted file mode 100644 index ef005ac2da55..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyFillPlaceholdersAction.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.StudyState; -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.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.editor.StudyEditor; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; - -public class StudyFillPlaceholdersAction extends AnAction { - @Override - public void actionPerformed(AnActionEvent e) { - final Project project = e.getProject(); - if (project != null) { - StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - StudyState studyState = new StudyState(studyEditor); - if (!studyState.isValid()) { - return; - } - TaskFile taskFile = studyState.getTaskFile(); - final Document document = studyState.getEditor().getDocument(); - for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) { - String answer = placeholder.getPossibleAnswer(); - if (answer == null) { - continue; - } - EduUtils.replaceAnswerPlaceholder(document, placeholder, placeholder.getRealLength(), answer); - } - EduUsagesCollector.placeholdersFilled(); - } - } - - @Override - public void update(AnActionEvent e) { - StudyUtils.updateAction(e); - final Project project = e.getProject(); - if (project != null) { - - Course course = StudyTaskManager.getInstance(project).getCourse(); - Presentation presentation = e.getPresentation(); - if (course != null && !EduNames.STUDY.equals(course.getCourseMode())) { - presentation.setEnabled(false); - presentation.setVisible(true); - return; - } - StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - StudyState studyState = new StudyState(studyEditor); - if (!studyState.isValid()) { - presentation.setEnabledAndVisible(false); - return; - } - TaskFile taskFile = studyState.getTaskFile(); - if (taskFile.getActivePlaceholders().isEmpty()) { - presentation.setEnabledAndVisible(false); - } - } - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyNextTaskAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyNextTaskAction.java deleted file mode 100644 index f0cd459f431f..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyNextTaskAction.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.icons.AllIcons; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class StudyNextTaskAction extends StudyTaskNavigationAction { - - public static final String ACTION_ID = "NextTaskAction"; - public static final String SHORTCUT = "ctrl pressed PERIOD"; - - public StudyNextTaskAction() { - super("Next Task", "Navigate to the next task", AllIcons.Actions.Forward); - } - - @Override - protected Task getTargetTask(@NotNull final Task sourceTask) { - return StudyNavigator.nextTask(sourceTask); - } - - @NotNull - @Override - public String getActionId() { - return ACTION_ID; - } - - @Nullable - @Override - public String[] getShortcuts() { - return new String[]{SHORTCUT}; - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java deleted file mode 100644 index b6f53e9be156..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyNextWindowAction.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.icons.AllIcons; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -/** - * move caret to next task window - */ -public class StudyNextWindowAction extends StudyWindowNavigationAction { - public static final String ACTION_ID = "NextWindow"; - public static final String SHORTCUT = "ctrl shift pressed PERIOD"; - public static final String SHORTCUT2 = "ctrl pressed ENTER"; - - public StudyNextWindowAction() { - super("Navigate to the Next Answer Placeholder", "Navigate to the next answer placeholder", AllIcons.Actions.Forward); - } - - @Override - protected AnswerPlaceholder getTargetPlaceholder(@NotNull final TaskFile taskFile, int offset) { - final AnswerPlaceholder selectedAnswerPlaceholder = taskFile.getAnswerPlaceholder(offset); - final List placeholders = taskFile.getActivePlaceholders(); - if (selectedAnswerPlaceholder == null) { - for (AnswerPlaceholder placeholder : placeholders) { - if (placeholder.getOffset() > offset) { - return placeholder; - } - } - } - else { - int index = selectedAnswerPlaceholder.getIndex(); - if (StudyUtils.indexIsValid(index, placeholders)) { - int newIndex = index + 1; - return placeholders.get(newIndex == placeholders.size() ? 0 : newIndex); - } - } - return null; - } - - @NotNull - @Override - public String getActionId() { - return ACTION_ID; - } - - @Nullable - @Override - public String[] getShortcuts() { - return new String[]{SHORTCUT, SHORTCUT2}; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java deleted file mode 100644 index 68f18d7da411..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyPrevWindowAction.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.icons.AllIcons; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -/** - * author: liana - * data: 6/30/14. - */ -public class StudyPrevWindowAction extends StudyWindowNavigationAction { - public static final String ACTION_ID = "PrevWindowAction"; - public static final String SHORTCUT = "ctrl shift pressed COMMA"; - - public StudyPrevWindowAction() { - super("Navigate to the Previous Answer Placeholder", "Navigate to the previous answer placeholder", - AllIcons.Actions.Back); - } - - - @Nullable - @Override - protected AnswerPlaceholder getTargetPlaceholder(@NotNull final TaskFile taskFile, int offset) { - final AnswerPlaceholder selectedAnswerPlaceholder = taskFile.getAnswerPlaceholder(offset); - final List placeholders = taskFile.getActivePlaceholders(); - if (selectedAnswerPlaceholder == null) { - for (int i = placeholders.size() - 1; i >= 0; i--) { - final AnswerPlaceholder placeholder = placeholders.get(i); - if (placeholder.getOffset() < offset) { - return placeholder; - } - } - } - else { - int prevIndex = selectedAnswerPlaceholder.getIndex() - 1; - if (StudyUtils.indexIsValid(prevIndex, placeholders)) { - return placeholders.get(prevIndex); - } - } - return null; - } - - @NotNull - @Override - public String getActionId() { - return ACTION_ID; - } - - @Nullable - @Override - public String[] getShortcuts() { - return new String[]{SHORTCUT}; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyPreviousTaskAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyPreviousTaskAction.java deleted file mode 100644 index 194c983f282d..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyPreviousTaskAction.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jetbrains.edu.learning.actions; - - -import com.intellij.icons.AllIcons; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class StudyPreviousTaskAction extends StudyTaskNavigationAction { - public StudyPreviousTaskAction() { - super("Previous Task","Navigate to the previous task", AllIcons.Actions.Back); - } - - public static final String ACTION_ID = "PreviousTaskAction"; - public static final String SHORTCUT = "ctrl pressed COMMA"; - - @Override - protected Task getTargetTask(@NotNull final Task sourceTask) { - return StudyNavigator.previousTask(sourceTask); - } - - @NotNull - @Override - public String getActionId() { - return ACTION_ID; - } - - @Nullable - @Override - public String[] getShortcuts() { - return new String[]{SHORTCUT}; - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java deleted file mode 100644 index 8c6ea0bd36cf..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.icons.AllIcons; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudySubtaskUtils; -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.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.editor.StudyEditor; -import org.jetbrains.annotations.Nullable; - -public class StudyRefreshAnswerPlaceholder extends DumbAwareAction { - - public static final String NAME = "Refresh Answer Placeholder"; - - public StudyRefreshAnswerPlaceholder() { - super(NAME, NAME, AllIcons.Actions.Refresh); - } - - @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - if (project == null) { - return; - } - final AnswerPlaceholder answerPlaceholder = getAnswerPlaceholder(e); - if (answerPlaceholder == null) { - return; - } - StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - if (studyEditor != null) { - StudySubtaskUtils.refreshPlaceholder(studyEditor.getEditor(), answerPlaceholder); - final StudyTaskManager taskManager = StudyTaskManager.getInstance(project); - answerPlaceholder.reset(); - taskManager.setStatus(answerPlaceholder, StudyStatus.Unchecked); - } - } - - @Override - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabledAndVisible(false); - Project project = e.getProject(); - if (project == null) { - return; - } - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - - if (!EduNames.STUDY.equals(course.getCourseMode())) { - presentation.setVisible(true); - return; - } - - if (getAnswerPlaceholder(e) == null) { - presentation.setEnabledAndVisible(false); - return; - } - presentation.setEnabledAndVisible(true); - } - - @Nullable - private static AnswerPlaceholder getAnswerPlaceholder(AnActionEvent e) { - final Project project = e.getProject(); - if (project == null) { - return null; - } - StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - final StudyState studyState = new StudyState(studyEditor); - if (studyEditor == null || !studyState.isValid()) { - return null; - } - final Editor editor = studyState.getEditor(); - final TaskFile taskFile = studyState.getTaskFile(); - return taskFile.getAnswerPlaceholder(editor.getCaretModel().getOffset()); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java deleted file mode 100644 index 12c146f79e6f..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.popup.Balloon; -import com.intellij.openapi.ui.popup.BalloonBuilder; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.problems.WolfTheProblemSolver; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudySubtaskUtils; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.*; -import com.jetbrains.edu.learning.editor.StudyChoiceVariantsPanel; -import com.jetbrains.edu.learning.editor.StudyEditor; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import com.jetbrains.edu.learning.ui.StudyToolWindow; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; - -public class StudyRefreshTaskFileAction extends StudyActionWithShortcut { - public static final String ACTION_ID = "RefreshTaskAction"; - public static final String SHORTCUT = "ctrl shift pressed X"; - private static final Logger LOG = Logger.getInstance(StudyRefreshTaskFileAction.class.getName()); - private static final String TEXT = "Reset Task File"; - private static final String RESET_TASK = "Reset Task"; - - public StudyRefreshTaskFileAction() { - super(TEXT, "Refresh current task", EducationalCoreIcons.ResetTaskFile); - } - - public static void refresh(@NotNull final Project project) { - ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { - StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - StudyState studyState = new StudyState(studyEditor); - if (studyEditor == null || !studyState.isValid()) { - LOG.info("RefreshTaskFileAction was invoked outside of Study Editor"); - return; - } - refreshFile(studyState, project); - EduUsagesCollector.fileRefreshed(); - })); - } - - private static void refreshFile(@NotNull final StudyState studyState, @NotNull final Project project) { - final Editor editor = studyState.getEditor(); - final TaskFile taskFile = studyState.getTaskFile(); - final Task task = taskFile.getTask(); - if (task instanceof TaskWithSubtasks) { - for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) { - StudySubtaskUtils.refreshPlaceholder(editor, placeholder); - } - } - else { - if (!resetTaskFile(editor.getDocument(), project, taskFile)) { - Messages.showInfoMessage("The initial text of task file is unavailable", "Failed to Refresh Task File"); - return; - } - if (task instanceof ChoiceTask) { - final StudyToolWindow window = StudyUtils.getStudyToolWindow(project); - if (window != null) { - window.setBottomComponent(new StudyChoiceVariantsPanel((ChoiceTask)task)); - } - } - } - WolfTheProblemSolver.getInstance(project).clearProblems(studyState.getVirtualFile()); - taskFile.setHighlightErrors(false); - StudyUtils.drawAllAnswerPlaceholders(editor, taskFile); - EduAnswerPlaceholderPainter.createGuardedBlocks(editor, taskFile); - ApplicationManager.getApplication().invokeLater( - () -> IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true)); - - StudyNavigator.navigateToFirstAnswerPlaceholder(editor, taskFile); - showBalloon(project, MessageType.INFO); - } - - private static boolean resetTaskFile(@NotNull final Document document, - @NotNull final Project project, - TaskFile taskFile) { - resetDocument(document, taskFile); - final Task task = taskFile.getTask(); - task.setStatus(StudyStatus.Unchecked); - if (task instanceof ChoiceTask) { - ((ChoiceTask)task).setSelectedVariants(new ArrayList<>()); - } - resetAnswerPlaceholders(taskFile, project); - ProjectView.getInstance(project).refresh(); - StudyUtils.updateToolWindows(project); - return true; - } - - private static void showBalloon(@NotNull final Project project, @NotNull final MessageType messageType) { - BalloonBuilder balloonBuilder = - JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can start again now", messageType, null); - final Balloon balloon = balloonBuilder.createBalloon(); - StudyEditor selectedStudyEditor = StudyUtils.getSelectedStudyEditor(project); - assert selectedStudyEditor != null; - balloon.show(StudyUtils.computeLocation(selectedStudyEditor.getEditor()), Balloon.Position.above); - Disposer.register(project, balloon); - } - - private static void resetAnswerPlaceholders(TaskFile selectedTaskFile, Project project) { - final StudyTaskManager taskManager = StudyTaskManager.getInstance(project); - for (AnswerPlaceholder answerPlaceholder : selectedTaskFile.getActivePlaceholders()) { - answerPlaceholder.reset(); - taskManager.setStatus(answerPlaceholder, StudyStatus.Unchecked); - } - } - - - private static void resetDocument(@NotNull final Document document, - @NotNull final TaskFile taskFile) { - StudyUtils.deleteGuardedBlocks(document); - taskFile.setTrackChanges(false); - clearDocument(document); - - document.setText(taskFile.text); - taskFile.setTrackChanges(true); - } - - private static void clearDocument(@NotNull final Document document) { - final int lineCount = document.getLineCount(); - if (lineCount != 0) { - CommandProcessor.getInstance().runUndoTransparentAction(() -> document.deleteString(0, document.getLineEndOffset(lineCount - 1))); - } - } - - public void actionPerformed(@NotNull AnActionEvent event) { - final Project project = event.getProject(); - if (project != null) { - refresh(project); - } - } - - @Override - public void update(AnActionEvent event) { - StudyUtils.updateAction(event); - final Project project = event.getProject(); - if (project != null) { - StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - StudyState studyState = new StudyState(studyEditor); - Presentation presentation = event.getPresentation(); - if (!studyState.isValid()) { - presentation.setEnabled(false); - return; - } - - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - Task task = StudyUtils.getCurrentTask(project); - if (task == null) { - return; - } - presentation.setText(task instanceof PyCharmTask ? TEXT : RESET_TASK); - if (!EduNames.STUDY.equals(course.getCourseMode())) { - presentation.setVisible(true); - presentation.setEnabled(false); - } - } - } - - @NotNull - @Override - public String getActionId() { - return ACTION_ID; - } - - @Nullable - @Override - public String[] getShortcuts() { - return new String[]{SHORTCUT}; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java deleted file mode 100644 index 683abc596daf..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyShowHintAction.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.ide.DataManager; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DataContext; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.ui.popup.PopupPositionManager; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import com.jetbrains.edu.learning.ui.StudyHint; -import com.jetbrains.edu.learning.ui.StudyToolWindow; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.awt.*; - -public class StudyShowHintAction extends StudyActionWithShortcut { - public static final String ACTION_ID = "ShowHintAction"; - public static final String SHORTCUT = "ctrl pressed 7"; - - public StudyShowHintAction() { - super("Show hint", "Show hint", EducationalCoreIcons.ShowHint); - } - - public void actionPerformed(@NotNull AnActionEvent e) { - final Project project = e.getProject(); - if (project == null) { - return; - } - showHint(project); - } - - public void showHint(Project project) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - StudyState studyState = new StudyState(StudyUtils.getSelectedStudyEditor(project)); - if (!studyState.isValid()) { - return; - } - PsiFile file = PsiManager.getInstance(project).findFile(studyState.getVirtualFile()); - final Editor editor = studyState.getEditor(); - int offset = editor.getCaretModel().getOffset(); - AnswerPlaceholder answerPlaceholder = studyState.getTaskFile().getAnswerPlaceholder(offset); - if (file == null) { - return; - } - EduUsagesCollector.hintShown(); - - final StudyToolWindow hintComponent = getHint(project, answerPlaceholder).getStudyToolWindow(); - hintComponent.setPreferredSize(new Dimension(400, 150)); - showHintPopUp(project, studyState, editor, hintComponent); - } - - @NotNull - protected StudyHint getHint(Project project, AnswerPlaceholder answerPlaceholder) { - return new StudyHint(answerPlaceholder, project); - } - - private static void showHintPopUp(Project project, StudyState studyState, Editor editor, StudyToolWindow hintComponent) { - final JBPopup popup = - JBPopupFactory.getInstance().createComponentPopupBuilder(hintComponent, hintComponent) - .setDimensionServiceKey(project, "StudyHint", false) - .setResizable(true) - .setMovable(true) - .setRequestFocus(true) - .setTitle(studyState.getTask().getName()) - .createPopup(); - Disposer.register(popup, hintComponent); - - final Component focusOwner = IdeFocusManager.getInstance(project).getFocusOwner(); - DataContext dataContext = DataManager.getInstance().getDataContext(focusOwner); - PopupPositionManager.positionPopupInBestPosition(popup, editor, dataContext); - } - - @Override - public void update(@NotNull AnActionEvent e) { - final Project project = e.getProject(); - if (project != null) { - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course != null && course.isAdaptive()) { - e.getPresentation().setEnabled(false); - return; - } - } - - StudyUtils.updateAction(e); - } - - @NotNull - @Override - public String getActionId() { - return ACTION_ID; - } - - @Nullable - @Override - public String[] getShortcuts() { - return new String[]{SHORTCUT}; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudySwitchTaskPanelAction.kt b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudySwitchTaskPanelAction.kt deleted file mode 100644 index 2b15f0bbd192..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudySwitchTaskPanelAction.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.jetbrains.edu.learning.actions - -import com.intellij.openapi.actionSystem.ActionPlaces.ACTION_SEARCH -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.ui.ComboBox -import com.intellij.openapi.ui.DialogWrapper -import com.jetbrains.edu.learning.StudySettings -import com.jetbrains.edu.learning.StudyUtils -import javax.swing.DefaultComboBoxModel -import javax.swing.JComponent -import javax.swing.JLabel - - -class StudySwitchTaskPanelAction: AnAction() { - - override fun actionPerformed(e: AnActionEvent?) { - val project = e?.project - val result = createDialog().showAndGet() - if (result && project != null) { - StudyUtils.initToolWindows(project) - } - } - - fun createDialog(): DialogWrapper { - return MyDialog(false) - } - - class MyDialog(canBeParent: Boolean) : DialogWrapper(null, canBeParent) { - val JAVAFX_ITEM = "JavaFX" - val SWING_ITEM = "Swing" - private val myComboBox: ComboBox = ComboBox() - - override fun createCenterPanel(): JComponent? { - return myComboBox - } - - override fun createNorthPanel(): JComponent? { - return JLabel("Choose panel: ") - } - - override fun getPreferredFocusedComponent(): JComponent? { - return myComboBox - } - - override fun doOKAction() { - super.doOKAction() - StudySettings.getInstance().setShouldUseJavaFx(myComboBox.selectedItem == JAVAFX_ITEM) - } - - init { - val comboBoxModel = DefaultComboBoxModel() - if (StudyUtils.hasJavaFx()) { - comboBoxModel.addElement(JAVAFX_ITEM) - } - comboBoxModel.addElement(SWING_ITEM) - comboBoxModel.selectedItem = - if (StudyUtils.hasJavaFx() && StudySettings.getInstance().shouldUseJavaFx()) JAVAFX_ITEM else SWING_ITEM - myComboBox.model = comboBoxModel - title = "Switch Task Description Panel" - myComboBox.setMinimumAndPreferredWidth(250) - init() - } - } - - override fun update(e: AnActionEvent?) { - val place = e?.place - val project = e?.project - e?.presentation?.isEnabled = project != null && StudyUtils.isStudyProject(project) || ACTION_SEARCH == place - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java deleted file mode 100644 index ce206a94c5ad..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyTaskNavigationAction.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.editor.StudyEditor; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - - -abstract public class StudyTaskNavigationAction extends StudyActionWithShortcut { - public StudyTaskNavigationAction(@Nullable String text, @Nullable String description, @Nullable Icon icon) { - super(text, description, icon); - } - - public void navigateTask(@NotNull final Project project) { - StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - StudyState studyState = new StudyState(studyEditor); - if (!studyState.isValid()) { - return; - } - Task targetTask = getTargetTask(studyState.getTask()); - if (targetTask == null) { - return; - } - - StudyNavigator.navigateToTask(project, targetTask); - - } - - @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - if (project == null) { - return; - } - navigateTask(project); - } - - protected abstract Task getTargetTask(@NotNull final Task sourceTask); - - @Override - public void update(AnActionEvent e) { - StudyUtils.updateAction(e); - Project project = e.getProject(); - if (project == null) { - return; - } - StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); - StudyState studyState = new StudyState(studyEditor); - if (!studyState.isValid()) { - return; - } - if (getTargetTask(studyState.getTask()) == null) { - e.getPresentation().setEnabled(false); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyUpdateRecommendationAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyUpdateRecommendationAction.java deleted file mode 100644 index 37aa13fe7ac2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyUpdateRecommendationAction.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; -import icons.EducationalCoreIcons; - -public class StudyUpdateRecommendationAction extends DumbAwareAction { - - public StudyUpdateRecommendationAction() { - super("Synchronize Course", "Synchronize Course", EducationalCoreIcons.StepikRefresh); - } - - @Override - public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - assert project != null; - - Course course = StudyTaskManager.getInstance(project).getCourse(); - assert course != null; - - Lesson adaptiveLesson = course.getLessons().get(0); - assert adaptiveLesson != null; - - int taskNumber = adaptiveLesson.getTaskList().size(); - Task lastRecommendationInCourse = adaptiveLesson.getTaskList().get(taskNumber - 1); - Task lastRecommendationOnStepik = ProgressManager.getInstance().runProcessWithProgressSynchronously( - () -> { - ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - return StudyUtils.execCancelable(() -> EduAdaptiveStepicConnector.getNextRecommendation(project, (RemoteCourse)course)); - }, - "Synchronizing Course", true, project); - - if (lastRecommendationOnStepik != null && lastRecommendationOnStepik.getStepId() != lastRecommendationInCourse.getStepId()) { - lastRecommendationOnStepik.initTask(adaptiveLesson, false); - EduAdaptiveStepicConnector.replaceCurrentTask(project, lastRecommendationOnStepik, adaptiveLesson); - ApplicationManager.getApplication().invokeLater(() -> { - VirtualFileManager.getInstance().refreshWithoutFileWatcher(false); - ProjectView.getInstance(project).refresh(); - StudyNavigator.navigateToTask(project, lastRecommendationOnStepik); - }); - } - } - - @Override - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - - Project project = e.getProject(); - if (project == null) { - presentation.setEnabledAndVisible(false); - return; - } - - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null || !course.isAdaptive()) { - presentation.setEnabledAndVisible(false); - return; - } - - presentation.setEnabledAndVisible(true); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java deleted file mode 100644 index 722ed099ff67..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyWindowNavigationAction.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -abstract public class StudyWindowNavigationAction extends StudyActionWithShortcut implements DumbAware { - - protected StudyWindowNavigationAction(String actionId, String description, Icon icon) { - super(actionId, description, icon); - } - - private void navigateToPlaceholder(@NotNull final Project project) { - final Editor selectedEditor = StudyUtils.getSelectedEditor(project); - if (selectedEditor != null) { - final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); - final VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument()); - if (openedFile != null) { - final TaskFile selectedTaskFile = StudyUtils.getTaskFile(project, openedFile); - if (selectedTaskFile != null) { - final int offset = selectedEditor.getCaretModel().getOffset(); - final AnswerPlaceholder targetPlaceholder = getTargetPlaceholder(selectedTaskFile, offset); - if (targetPlaceholder == null) { - return; - } - StudyNavigator.navigateToAnswerPlaceholder(selectedEditor, targetPlaceholder); - } - } - } - } - - @Nullable - protected abstract AnswerPlaceholder getTargetPlaceholder(@NotNull final TaskFile taskFile, int offset); - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final Project project = e.getProject(); - if (project == null) { - return; - } - navigateToPlaceholder(project); - } - - @Override - public void update(@NotNull AnActionEvent e) { - StudyUtils.updateAction(e); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduBuiltInServerUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduBuiltInServerUtils.java deleted file mode 100644 index a79f1a15a279..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduBuiltInServerUtils.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2000-2017 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.builtInServer; - -import com.intellij.ide.RecentProjectsManagerBase; -import com.intellij.ide.impl.ProjectUtil; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.TransactionGuard; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.util.xmlb.XmlSerializationException; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.newproject.ui.EduCreateNewProjectDialog; -import com.jetbrains.edu.learning.newproject.ui.EduCreateNewStepikProjectDialog; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jdom.input.SAXBuilder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -import static com.jetbrains.edu.learning.StudyUtils.navigateToStep; -import static com.jetbrains.edu.learning.core.EduNames.STUDY_PROJECT_XML_PATH; -import static com.jetbrains.edu.learning.stepic.EduStepicNames.STEP_ID; - -public class EduBuiltInServerUtils { - public static boolean focusOpenProject(int courseId, int stepId) { - Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); - for (Project project : openProjects) { - if (!project.isDefault()) { - StudyTaskManager taskManager = StudyTaskManager.getInstance(project); - if (taskManager != null) { - Course course = taskManager.getCourse(); - RemoteCourse remoteCourse = course instanceof RemoteCourse ? (RemoteCourse)course : null; - if (remoteCourse != null && remoteCourse.getId() == courseId) { - ApplicationManager.getApplication().invokeLater(() -> { - requestFocus(project); - navigateToStep(project, course, stepId); - }); - return true; - } - } - } - } - return false; - } - - @Nullable - private static Project openProject(@NotNull String projectPath) { - final Project[] project = {null}; - ApplicationManager.getApplication().invokeAndWait(() -> { - TransactionGuard.getInstance().submitTransactionAndWait(() -> - project[0] = ProjectUtil.openProject(projectPath, null, true)); - requestFocus(project[0]); - }); - return project[0]; - } - - private static void requestFocus(@NotNull Project project) { - ProjectUtil.focusProjectWindow(project, false); - } - - public static boolean openRecentProject(int targetCourseId, int stepId) { - RecentProjectsManagerBase recentProjectsManager = RecentProjectsManagerBase.getInstanceEx(); - - if (recentProjectsManager == null) { - return false; - } - - RecentProjectsManagerBase.State state = recentProjectsManager.getState(); - - if (state == null) { - return false; - } - - List recentPaths = state.recentPaths; - - SAXBuilder parser = new SAXBuilder(); - - for (String projectPath : recentPaths) { - Element component = readComponent(parser, projectPath); - if (component == null) { - continue; - } - StudyTaskManager taskManager = getDefaultTaskManager(); - int courseId = getCourseId(taskManager, component); - - if (courseId == targetCourseId) { - PropertiesComponent.getInstance().setValue(STEP_ID, stepId, 0); - Project project = openProject(projectPath); - if (project != null) { - return true; - } - } - } - return false; - } - - - @Nullable - private static Element readComponent(@NotNull SAXBuilder parser, @NotNull String projectPath) { - Element component = null; - try { - String studyProjectXML = projectPath + STUDY_PROJECT_XML_PATH; - Document xmlDoc = parser.build(new File(studyProjectXML)); - Element root = xmlDoc.getRootElement(); - component = root.getChild("component"); - } - catch (JDOMException | IOException ignored) { - } - - return component; - } - - private static int getCourseId(@NotNull StudyTaskManager taskManager, @NotNull Element component) { - try { - taskManager.loadState(component); - Course course = taskManager.getCourse(); - - if (course instanceof RemoteCourse) { - return ((RemoteCourse)course).getId(); - } - } - catch (IllegalStateException | XmlSerializationException ignored) { - } - return 0; - } - - public static boolean createProject(int courseId, int stepId) { - ApplicationManager.getApplication().invokeLater(() -> { - PropertiesComponent.getInstance().setValue(STEP_ID, stepId, 0); - EduCreateNewProjectDialog createNewProjectDlg = new EduCreateNewStepikProjectDialog(courseId); - createNewProjectDlg.show(); - }); - - return true; - } - @NotNull - private static StudyTaskManager getDefaultTaskManager() { - Project defaultProject = ProjectManager.getInstance().getDefaultProject(); - return StudyTaskManager.getInstance(defaultProject); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduStepikRestService.java b/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduStepikRestService.java deleted file mode 100644 index 2b8e1f953486..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/builtInServer/EduStepikRestService.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2000-2017 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.builtInServer; - -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationType; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ApplicationNamesInfo; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; -import com.intellij.openapi.util.io.StreamUtil; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.ui.AppIcon; -import com.jetbrains.edu.learning.StudySettings; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.stepic.EduStepicAuthorizedClient; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicUser; -import com.jetbrains.edu.learning.stepic.StepicWrappers; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.ide.RestService; -import org.jetbrains.io.Responses; - -import javax.swing.*; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.nio.charset.Charset; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.jetbrains.edu.learning.builtInServer.EduBuiltInServerUtils.*; -import static com.jetbrains.edu.learning.stepic.EduStepicNames.EDU_STEPIK_SERVICE_NAME; -import static com.jetbrains.edu.learning.stepic.EduStepicNames.LINK; - -public class EduStepikRestService extends RestService { - private static final Logger LOG = Logger.getInstance(EduStepikRestService.class.getName()); - private static final Pattern OPEN_COURSE_PATTERN = Pattern.compile("/" + EDU_STEPIK_SERVICE_NAME + "\\?link=.+"); - private static final Pattern COURSE_PATTERN = Pattern.compile("https://stepik\\.org/lesson(?:/[a-zA-Z\\-]*-|/)(\\d+)/step/(\\d+)"); - private static final Pattern - OAUTH_CODE_PATTERN = Pattern.compile("/" + RestService.PREFIX + "/" + EDU_STEPIK_SERVICE_NAME + "/oauth" + "\\?code=(\\w+)"); - - @NotNull - private static String log(@NotNull String message) { - LOG.info(message); - return message; - } - - @NotNull - @Override - protected String getServiceName() { - return EDU_STEPIK_SERVICE_NAME; - } - - @Override - protected boolean isMethodSupported(@NotNull HttpMethod method) { - return method == HttpMethod.GET; - } - - @Override - protected boolean isPrefixlessAllowed() { - return true; - } - - @Override - protected boolean isHostTrusted(@NotNull FullHttpRequest request) throws InterruptedException, InvocationTargetException { - String uri = request.uri(); - Matcher codeMatcher = OAUTH_CODE_PATTERN.matcher(uri); - if (request.method() == HttpMethod.GET && codeMatcher.matches()) { - return true; - } - return super.isHostTrusted(request); - } - - @Nullable - @Override - public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) - throws IOException { - String uri = urlDecoder.uri(); - LOG.info("Request: " + uri); - - Matcher matcher = OPEN_COURSE_PATTERN.matcher(uri); - if (matcher.matches()) { - int courseId; - int stepId; - String link = getStringParameter(LINK, urlDecoder); - - if (link == null) { - return log("The link parameter was not found"); - } - - LOG.info("Try to open a course: " + link); - - QueryStringDecoder linkDecoder = new QueryStringDecoder(link); - - matcher = COURSE_PATTERN.matcher(linkDecoder.path()); - - if (!matcher.matches()) { - return log("Unrecognized the link parameter"); - } - - int lessonId; - int stepIndex; - try { - lessonId = Integer.parseInt(matcher.group(1)); - stepIndex = Integer.parseInt(matcher.group(2)); - } catch (NumberFormatException e) { - return log("Unrecognized the link"); - } - - int unitId = getIntParameter("unit", linkDecoder); - - if (unitId == -1) { - return log("Unrecognized the Unit id"); - } - - StepicWrappers.Unit unit = EduStepicConnector.getUnit(unitId); - if (unit.getId() == 0) { - return log("Unrecognized the Unit id"); - } - - StepicWrappers.Section section = EduStepicConnector.getSection(unit.getSection()); - courseId = section.getCourse(); - if (courseId == 0) { - return log("Unrecognized the course id"); - } - Lesson lesson = EduStepicConnector.getLesson(lessonId); - List stepIds = lesson.steps; - - if (stepIds.isEmpty()) { - return log("Unrecognized the step id"); - } - stepId = stepIds.get(stepIndex - 1); - - LOG.info(String.format("Try to open a course: courseId=%s, stepId=%s", courseId, stepId)); - - if (focusOpenProject(courseId, stepId) || openRecentProject(courseId, stepId) || createProject(courseId, stepId)) { - RestService.sendOk(request, context); - LOG.info("Course opened: " + courseId); - return null; - } - - RestService.sendStatus(HttpResponseStatus.NOT_FOUND, false, context.channel()); - String message = "A project didn't found or created"; - LOG.info(message); - return message; - } - - Matcher codeMatcher = OAUTH_CODE_PATTERN.matcher(uri); - if (codeMatcher.matches()) { - String code = getStringParameter("code", urlDecoder); - if (code != null) { - StepicUser stepicUser = EduStepicAuthorizedClient.login(code, EduStepicConnector.getOAuthRedirectUrl()); - if (stepicUser != null) { - StudySettings.getInstance().setUser(stepicUser); - sendHtmlResponse(request, context, "/oauthResponsePages/okPage.html"); - showStepicNotification(NotificationType.INFORMATION, - "Logged in as " + stepicUser.getFirstName() + " " + stepicUser.getLastName()); - focusOnApplicationWindow(); - return null; - } - } - - sendHtmlResponse(request, context, "/oauthResponsePages/errorPage.html"); - showStepicNotification(NotificationType.ERROR, "Failed to log in"); - return "Couldn't find code parameter for Stepik OAuth"; - } - - RestService.sendStatus(HttpResponseStatus.BAD_REQUEST, false, context.channel()); - String message = "Unknown command: " + uri; - LOG.info(message); - return message; - } - - private static void focusOnApplicationWindow() { - JFrame frame = WindowManager.getInstance().findVisibleFrame(); - ApplicationManager.getApplication().invokeLater(() -> { - AppIcon.getInstance().requestFocus((IdeFrame)frame); - frame.toFront(); - }); - } - - private void sendHtmlResponse(@NotNull HttpRequest request, @NotNull ChannelHandlerContext context, String pagePath) throws IOException { - BufferExposingByteArrayOutputStream byteOut = new BufferExposingByteArrayOutputStream(); - InputStream pageTemplateStream = getClass().getResourceAsStream(pagePath); - String pageTemplate = StreamUtil.readText(pageTemplateStream, Charset.forName("UTF-8")); - try { - String pageWithProductName = pageTemplate.replaceAll("%IDE_NAME", ApplicationNamesInfo.getInstance().getFullProductName()); - byteOut.write(StreamUtil.loadFromStream(new ByteArrayInputStream(pageWithProductName.getBytes(Charset.forName("UTF-8"))))); - HttpResponse response = Responses.response("text/html", Unpooled.wrappedBuffer(byteOut.getInternalBuffer(), 0, byteOut.size())); - Responses.addNoCache(response); - response.headers().set("X-Frame-Options", "Deny"); - Responses.send(response, context.channel(), request); - } - finally { - byteOut.close(); - pageTemplateStream.close(); - } - } - - private static void showStepicNotification(@NotNull NotificationType notificationType, @NotNull String text) { - Notification notification = new Notification("Stepik", "Stepik", text, notificationType); - notification.notify(null); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/OutputTaskChecker.kt b/python/educational-core/src/com/jetbrains/edu/learning/checker/OutputTaskChecker.kt deleted file mode 100644 index 57c5f356b738..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/OutputTaskChecker.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.jetbrains.edu.learning.checker - -import com.intellij.execution.ExecutionListener -import com.intellij.execution.ExecutionManager -import com.intellij.execution.RunnerAndConfigurationSettings -import com.intellij.execution.RunnerRegistry -import com.intellij.execution.actions.ConfigurationContext -import com.intellij.execution.executors.DefaultRunExecutor -import com.intellij.execution.process.ProcessAdapter -import com.intellij.execution.process.ProcessEvent -import com.intellij.execution.process.ProcessOutputTypes -import com.intellij.execution.runners.ExecutionEnvironment -import com.intellij.execution.runners.ExecutionEnvironmentBuilder -import com.intellij.ide.DataManager -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Computable -import com.intellij.openapi.util.Key -import com.intellij.openapi.vfs.VfsUtil -import com.jetbrains.edu.learning.StudyUtils -import com.jetbrains.edu.learning.actions.StudyCheckAction -import com.jetbrains.edu.learning.courseFormat.StudyStatus -import com.jetbrains.edu.learning.courseFormat.tasks.OutputTask -import java.util.* -import java.util.concurrent.CountDownLatch - - -class OutputTaskChecker(task: OutputTask, project: Project) : StudyTaskChecker(task, project) { - companion object { - private val NOT_RUNNABLE_MESSAGE = "Solution isn't runnable" - private val OUTPUT_PATTERN_NAME = "output.txt" - } - - override fun onTaskFailed(message: String) { - super.onTaskFailed("Incorrect output") - StudyCheckUtils.showTestResultsToolWindow(myProject, message) - } - - override fun onTaskSolved(message: String) { - super.onTaskSolved(message) - } - - override fun check(): StudyCheckResult { - val configuration = getConfiguration() ?: return StudyCheckResult(StudyStatus.Unchecked, NOT_RUNNABLE_MESSAGE) - val executor = DefaultRunExecutor.getRunExecutorInstance() - val runner = RunnerRegistry.getInstance().getRunner(executor.id, configuration.configuration) - configuration.isActivateToolWindowBeforeRun = false - val env = ExecutionEnvironmentBuilder.create(executor, configuration).build() - var processNotStarted = false - val connection = myProject.messageBus.connect() - val latch = CountDownLatch(1) - val output = ArrayList() - connection.subscribe(ExecutionManager.EXECUTION_TOPIC, object : ExecutionListener { - override fun processNotStarted(executorId: String, e: ExecutionEnvironment) { - if (executorId == executor.id && e == env) { - latch.countDown() - processNotStarted = true - } - } - }) - runner?.execute(env) { - it.processHandler?.addProcessListener(object : ProcessAdapter() { - override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { - if (outputType == ProcessOutputTypes.STDOUT) { - output.add(event.text) - } - } - - override fun processTerminated(event: ProcessEvent) { - latch.countDown() - } - }) - } - - latch.await() - connection.disconnect() - if (processNotStarted) { - return StudyCheckResult(StudyStatus.Unchecked, NOT_RUNNABLE_MESSAGE) - } - - val outputPatternFile = myTask.getTaskDir(myProject)?.findChild(OUTPUT_PATTERN_NAME) - ?: return StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH) - val expectedOutput = VfsUtil.loadText(outputPatternFile) - var outputString = output.joinToString("\n") - if (outputString.isEmpty()) { - outputString = "" - } - if (expectedOutput == outputString) { - return StudyCheckResult(StudyStatus.Solved, StudyTestsOutputParser.CONGRATULATIONS) - } - return StudyCheckResult(StudyStatus.Failed, "Expected output:\n$expectedOutput \nActual output:\n$outputString") - } - - private fun getConfiguration(): RunnerAndConfigurationSettings? { - return ApplicationManager.getApplication().runReadAction(Computable { - val dataContext = DataManager.getInstance().getDataContext(StudyUtils.getSelectedEditor(myProject)?.component) - val configurationContext = ConfigurationContext.getFromContext(dataContext) - return@Computable configurationContext.configuration - }) - } - - override fun clearState() { - StudyCheckUtils.drawAllPlaceholders(myProject, myTask) - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckListener.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckListener.java deleted file mode 100644 index e4f2fa636028..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckListener.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.jetbrains.edu.learning.checker; - -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; - -public interface StudyCheckListener { - ExtensionPointName EP_NAME = ExtensionPointName.create("Edu.checkListener"); - - default void beforeCheck(@NotNull Project project, @NotNull Task task) {} - - default void afterCheck(@NotNull Project project, @NotNull Task task) {} -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckResult.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckResult.java deleted file mode 100644 index 3c1c5f62c942..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckResult.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.jetbrains.edu.learning.checker; - -import com.jetbrains.edu.learning.courseFormat.StudyStatus; - -public class StudyCheckResult { - private StudyStatus myStatus; - private String myMessage; - - public StudyCheckResult(StudyStatus status, String message) { - myStatus = status; - myMessage = message; - } - - public StudyStatus getStatus() { - return myStatus; - } - - public String getMessage() { - return myMessage; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java deleted file mode 100644 index 6a1971baa548..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.jetbrains.edu.learning.checker; - -import com.intellij.execution.impl.ConsoleViewImpl; -import com.intellij.execution.process.CapturingProcessHandler; -import com.intellij.execution.process.ProcessOutput; -import com.intellij.execution.ui.ConsoleViewContentType; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.Balloon; -import com.intellij.openapi.ui.popup.BalloonBuilder; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowAnchor; -import com.intellij.openapi.wm.ToolWindowManager; -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.EduUtils; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.editor.StudyEditor; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import com.jetbrains.edu.learning.ui.StudyTestResultsToolWindowFactory; -import com.jetbrains.edu.learning.ui.StudyTestResultsToolWindowFactoryKt; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.util.Map; - -public class StudyCheckUtils { - private static final Logger LOG = Logger.getInstance(StudyCheckUtils.class); - - private StudyCheckUtils() { - } - - public static void drawAllPlaceholders(@NotNull final Project project, @NotNull final Task task) { - VirtualFile taskDir = task.getTaskDir(project); - if (taskDir == null) { - return; - } - for (Map.Entry entry : task.getTaskFiles().entrySet()) { - String name = entry.getKey(); - TaskFile taskFile = entry.getValue(); - VirtualFile virtualFile = taskDir.findFileByRelativePath(name); - if (virtualFile == null) { - continue; - } - FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile); - if (fileEditor instanceof StudyEditor) { - StudyEditor studyEditor = (StudyEditor)fileEditor; - StudyUtils.drawAllAnswerPlaceholders(studyEditor.getEditor(), taskFile); - } - } - } - - public static void navigateToFailedPlaceholder(@NotNull final StudyState studyState, - @NotNull final Task task, - @NotNull final VirtualFile taskDir, - @NotNull final Project project) { - TaskFile selectedTaskFile = studyState.getTaskFile(); - Editor editor = studyState.getEditor(); - TaskFile taskFileToNavigate = selectedTaskFile; - VirtualFile fileToNavigate = studyState.getVirtualFile(); - final StudyTaskManager taskManager = StudyTaskManager.getInstance(project); - if (!taskManager.hasFailedAnswerPlaceholders(selectedTaskFile)) { - for (Map.Entry entry : task.getTaskFiles().entrySet()) { - String name = entry.getKey(); - TaskFile taskFile = entry.getValue(); - if (taskManager.hasFailedAnswerPlaceholders(taskFile)) { - taskFileToNavigate = taskFile; - VirtualFile virtualFile = taskDir.findFileByRelativePath(name); - if (virtualFile == null) { - continue; - } - FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile); - if (fileEditor instanceof StudyEditor) { - StudyEditor studyEditor = (StudyEditor)fileEditor; - editor = studyEditor.getEditor(); - } - fileToNavigate = virtualFile; - break; - } - } - } - if (fileToNavigate != null) { - FileEditorManager.getInstance(project).openFile(fileToNavigate, true); - } - final Editor editorToNavigate = editor; - ApplicationManager.getApplication().invokeLater( - () -> IdeFocusManager.getInstance(project).requestFocus(editorToNavigate.getContentComponent(), true)); - - StudyNavigator.navigateToFirstFailedAnswerPlaceholder(editor, taskFileToNavigate); - } - - - public static void showTestResultPopUp(@NotNull final String text, Color color, @NotNull final Project project) { - BalloonBuilder balloonBuilder = - JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, null, color, null); - final Balloon balloon = balloonBuilder.createBalloon(); - StudyUtils.showCheckPopUp(project, balloon); - } - - - public static void flushWindows(@NotNull final Task task, @NotNull final VirtualFile taskDir) { - for (Map.Entry entry : task.getTaskFiles().entrySet()) { - String name = entry.getKey(); - TaskFile taskFile = entry.getValue(); - VirtualFile virtualFile = taskDir.findFileByRelativePath(name); - if (virtualFile == null) { - continue; - } - EduUtils.flushWindows(taskFile, virtualFile); - } - } - - public static void showTestResultsToolWindow(@NotNull final Project project, @NotNull final String message) { - ApplicationManager.getApplication().invokeLater(() -> { - final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); - ToolWindow window = toolWindowManager.getToolWindow(StudyTestResultsToolWindowFactoryKt.ID); - if (window == null) { - toolWindowManager.registerToolWindow(StudyTestResultsToolWindowFactoryKt.ID, true, ToolWindowAnchor.BOTTOM); - window = toolWindowManager.getToolWindow(StudyTestResultsToolWindowFactoryKt.ID); - new StudyTestResultsToolWindowFactory().createToolWindowContent(project, window); - } - - final Content[] contents = window.getContentManager().getContents(); - for (Content content : contents) { - final JComponent component = content.getComponent(); - if (component instanceof ConsoleViewImpl) { - ((ConsoleViewImpl)component).clear(); - ((ConsoleViewImpl)component).print(message, ConsoleViewContentType.ERROR_OUTPUT); - window.setAvailable(true,null); - window.show(null); - } - } - }); - } - - public static StudyTestsOutputParser.TestsOutput getTestOutput(@NotNull Process testProcess, - @NotNull String commandLine, - boolean isAdaptive) { - final CapturingProcessHandler handler = new CapturingProcessHandler(testProcess, null, commandLine); - final ProcessOutput output = ProgressManager.getInstance().hasProgressIndicator() ? handler - .runProcessWithProgressIndicator(ProgressManager.getInstance().getProgressIndicator()) : - handler.runProcess(); - final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output, isAdaptive); - String stderr = output.getStderr(); - if (!stderr.isEmpty() && output.getStdout().isEmpty()) { - LOG.info("#educational " + stderr); - return new StudyTestsOutputParser.TestsOutput(false, stderr); - } - return testsOutput; - } - - public static void hideTestResultsToolWindow(@NotNull Project project) { - ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(StudyTestResultsToolWindowFactoryKt.ID); - if (toolWindow != null) { - toolWindow.hide(() -> {}); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTaskChecker.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTaskChecker.java deleted file mode 100644 index dc02bcd90307..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTaskChecker.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.jetbrains.edu.learning.checker; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.stepic.StepicUser; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class StudyTaskChecker { - @NotNull protected final T myTask; - @NotNull protected final Project myProject; - - public StudyTaskChecker(@NotNull T task, @NotNull Project project) { - myTask = task; - myProject = project; - } - - public void onTaskSolved(@NotNull String message) { - ApplicationManager.getApplication().invokeLater( - () -> StudyCheckUtils.showTestResultPopUp(message, MessageType.INFO.getPopupBackground(), myProject)); - } - - public void onTaskFailed(@NotNull String message) { - ApplicationManager.getApplication() - .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject)); - } - - public StudyCheckResult check() { - return new StudyCheckResult(StudyStatus.Unchecked, "Check for " + myTask.getTaskType() + " task isn't available"); - } - - public StudyCheckResult checkOnRemote(@Nullable StepicUser user) { - return new StudyCheckResult(StudyStatus.Unchecked, "Remote check for " + myTask.getTaskType() + " task isn't available"); - } - - public void clearState() { - - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java deleted file mode 100644 index 90cf27d4c958..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java +++ /dev/null @@ -1,72 +0,0 @@ -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 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 "; - public static final String CONGRATULATIONS = "Congratulations!"; - - public static class TestsOutput { - private final boolean isSuccess; - private final String myMessage; - - public TestsOutput(boolean isSuccess, @NotNull final String message) { - this.isSuccess = isSuccess; - myMessage = message; - } - - public boolean isSuccess() { - return isSuccess; - } - - public String getMessage() { - return myMessage; - } - } - - @NotNull - public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput, final boolean isAdaptive) { - String congratulations = CONGRATULATIONS; - final List 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; - } - - if (line.contains(CONGRATS_MESSAGE)) { - congratulations = line.substring(line.indexOf(CONGRATS_MESSAGE) + CONGRATS_MESSAGE.length()); - } - - if (line.contains(TEST_FAILED)) { - 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()); - } - } - } - } - - return new TestsOutput(true, congratulations); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/TaskWithSubtasksChecker.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/TaskWithSubtasksChecker.java deleted file mode 100644 index 68bfca862beb..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/TaskWithSubtasksChecker.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.jetbrains.edu.learning.checker; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudySubtaskUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import com.jetbrains.edu.learning.stepic.StepicUser; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - -public class TaskWithSubtasksChecker extends StudyTaskChecker { - private StudyTaskChecker myPyCharmTaskChecker; - public TaskWithSubtasksChecker(@NotNull TaskWithSubtasks task, - @NotNull Project project) { - super(task, project); - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(myTask.getLesson().getCourse().getLanguageById()); - if (configurator != null) { - myPyCharmTaskChecker = configurator.getPyCharmTaskChecker(task, project); - } - } - - @Override - public StudyCheckResult check() { - if (myPyCharmTaskChecker != null) { - return myPyCharmTaskChecker.check(); - } - return super.check(); - } - - @Override - public StudyCheckResult checkOnRemote(@Nullable StepicUser user) { - if (myPyCharmTaskChecker != null) { - return myPyCharmTaskChecker.checkOnRemote(user); - } - return super.checkOnRemote(user); - } - - @Override - public void onTaskSolved(@NotNull String message) { - boolean hasMoreSubtasks = myTask.activeSubtaskNotLast(); - final int activeSubtaskIndex = myTask.getActiveSubtaskIndex(); - int visibleSubtaskIndex = activeSubtaskIndex + 1; - ApplicationManager.getApplication().invokeLater(() -> { - int subtaskSize = myTask.getLastSubtaskIndex() + 1; - String resultMessage = !hasMoreSubtasks ? message : "Subtask " + visibleSubtaskIndex + "/" + subtaskSize + " solved"; - StudyCheckUtils.showTestResultPopUp(resultMessage, MessageType.INFO.getPopupBackground(), myProject); - if (hasMoreSubtasks) { - int nextSubtaskIndex = activeSubtaskIndex + 1; - StudySubtaskUtils.switchStep(myProject, myTask, nextSubtaskIndex); - rememberAnswers(nextSubtaskIndex, myTask); - } - }); - } - - private void rememberAnswers(int nextSubtaskIndex, @NotNull TaskWithSubtasks task) { - VirtualFile taskDir = task.getTaskDir(myProject); - if (taskDir == null) { - return; - } - VirtualFile srcDir = taskDir.findChild(EduNames.SRC); - if (srcDir != null) { - taskDir = srcDir; - } - for (Map.Entry entry : task.getTaskFiles().entrySet()) { - TaskFile taskFile = entry.getValue(); - VirtualFile virtualFile = taskDir.findFileByRelativePath(entry.getKey()); - if (virtualFile == null) { - continue; - } - Document document = FileDocumentManager.getInstance().getDocument(virtualFile); - if (document == null) { - continue; - } - for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) { - if (placeholder.getSubtaskInfos().containsKey(nextSubtaskIndex - 1)) { - int offset = placeholder.getOffset(); - String answer = document.getText(TextRange.create(offset, offset + placeholder.getRealLength())); - placeholder.getSubtaskInfos().get(nextSubtaskIndex - 1).setAnswer(answer); - } - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderDeleteHandler.java b/python/educational-core/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderDeleteHandler.java deleted file mode 100644 index 3b590003e5be..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderDeleteHandler.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.jetbrains.edu.learning.core; - -import com.intellij.codeInsight.hint.HintManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.ReadOnlyFragmentModificationException; -import com.intellij.openapi.editor.actionSystem.ReadonlyFragmentModificationHandler; -import org.jetbrains.annotations.NotNull; - -public class EduAnswerPlaceholderDeleteHandler implements ReadonlyFragmentModificationHandler { - - private final Editor myEditor; - - public EduAnswerPlaceholderDeleteHandler(@NotNull final Editor editor) { - myEditor = editor; - } - - @Override - public void handle(ReadOnlyFragmentModificationException e) { - HintManager.getInstance().showErrorHint(myEditor, "It's not allowed to delete answer placeholders"); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java b/python/educational-core/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java deleted file mode 100644 index bccea5e56a7e..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/core/EduAnswerPlaceholderPainter.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.jetbrains.edu.learning.core; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.colors.EditorColors; -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.editor.impl.DocumentImpl; -import com.intellij.openapi.editor.markup.*; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.ui.JBColor; -import com.intellij.ui.SimpleTextAttributes; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.awt.*; -import java.util.List; - -public class EduAnswerPlaceholderPainter { - - //it should be the lowest highlighting layer, otherwise selection and other effects are not visible - public static final int PLACEHOLDERS_LAYER = 0; - - private EduAnswerPlaceholderPainter() { - - } - - public static void drawAnswerPlaceholder(@NotNull final Editor editor, @NotNull final AnswerPlaceholder placeholder, - @NotNull final JBColor color) { - EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); - final TextAttributes textAttributes = new TextAttributes(scheme.getDefaultForeground(), scheme.getDefaultBackground(), null, - EffectType.BOXED, Font.PLAIN); - textAttributes.setEffectColor(color); - int startOffset = placeholder.getOffset(); - if (startOffset == -1) { - return; - } - final int length = - placeholder.isActive() ? placeholder.getRealLength() : placeholder.getVisibleLength(placeholder.getActiveSubtaskIndex()); - Pair offsets = StudyUtils.getPlaceholderOffsets(placeholder, editor.getDocument()); - startOffset = offsets.first; - int endOffset = offsets.second; - if (placeholder.isActive()) { - drawAnswerPlaceholder(editor, startOffset, endOffset, textAttributes, PLACEHOLDERS_LAYER); - } - else if (!placeholder.getUseLength() && length != 0) { - drawAnswerPlaceholderFromPrevStep(editor, startOffset, endOffset); - } - } - - public static void drawAnswerPlaceholder(@NotNull Editor editor, - int start, - int end, - @Nullable TextAttributes textAttributes, - int placeholdersLayer) { - final Project project = editor.getProject(); - assert project != null; - if (start == -1) { - return; - } - RangeHighlighter highlighter = editor.getMarkupModel().addRangeHighlighter(start, end, placeholdersLayer, - textAttributes, HighlighterTargetArea.EXACT_RANGE); - highlighter.setGreedyToLeft(true); - highlighter.setGreedyToRight(true); - } - - - public static void drawAnswerPlaceholderFromPrevStep(@NotNull Editor editor, int start, int end) { - EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); - Color color = scheme.getColor(EditorColors.TEARLINE_COLOR); - SimpleTextAttributes attributes = SimpleTextAttributes.GRAY_ATTRIBUTES; - final TextAttributes textAttributes = new TextAttributes(attributes.getFgColor(), color, null, - null, attributes.getFontStyle()); - - drawAnswerPlaceholder(editor, start, end, textAttributes, HighlighterLayer.LAST); - } - - public static void createGuardedBlock(Editor editor, List blocks, int start, int end) { - RangeHighlighter rh = editor.getMarkupModel() - .addRangeHighlighter(start, end, PLACEHOLDERS_LAYER, null, HighlighterTargetArea.EXACT_RANGE); - blocks.add(rh); - } - - - public static void createGuardedBlocks(@NotNull final Editor editor, TaskFile taskFile) { - for (AnswerPlaceholder answerPlaceholder : taskFile.getAnswerPlaceholders()) { - createGuardedBlocks(editor, answerPlaceholder); - } - } - - public static void createGuardedBlocks(@NotNull final Editor editor, AnswerPlaceholder placeholder) { - Document document = editor.getDocument(); - if (document instanceof DocumentImpl) { - DocumentImpl documentImpl = (DocumentImpl)document; - List blocks = documentImpl.getGuardedBlocks(); - Pair offsets = StudyUtils.getPlaceholderOffsets(placeholder, editor.getDocument()); - Integer start = offsets.first; - Integer end = offsets.second; - if (start != 0) { - createGuardedBlock(editor, blocks, start - 1, start); - } - if (end != document.getTextLength()) { - createGuardedBlock(editor, blocks, end, end + 1); - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/core/EduDocumentListener.java b/python/educational-core/src/com/jetbrains/edu/learning/core/EduDocumentListener.java deleted file mode 100644 index 4f75ed1d2645..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/core/EduDocumentListener.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.jetbrains.edu.learning.core; - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.event.DocumentEvent; -import com.intellij.openapi.editor.event.DocumentListener; -import com.intellij.openapi.editor.impl.event.DocumentEventImpl; -import com.intellij.openapi.util.TextRange; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; - -import java.util.ArrayList; -import java.util.List; - -/** - * Listens changes in study files and updates - * coordinates of all the windows in current task file - */ -public class EduDocumentListener implements DocumentListener { - private final TaskFile myTaskFile; - private final boolean myTrackLength; - private final List myAnswerPlaceholders = new ArrayList<>(); - - - public EduDocumentListener(TaskFile taskFile) { - myTaskFile = taskFile; - myTrackLength = true; - } - - public EduDocumentListener(TaskFile taskFile, boolean trackLength) { - myTaskFile = taskFile; - myTrackLength = trackLength; - } - - @Override - public void beforeDocumentChange(DocumentEvent e) { - if (!myTaskFile.isTrackChanges()) { - return; - } - myTaskFile.setHighlightErrors(true); - myAnswerPlaceholders.clear(); - for (AnswerPlaceholder answerPlaceholder : myTaskFile.getAnswerPlaceholders()) { - int twStart = answerPlaceholder.getOffset(); - int length = answerPlaceholder.getRealLength(); - int twEnd = twStart + length; - myAnswerPlaceholders.add(new AnswerPlaceholderWrapper(answerPlaceholder, twStart, twEnd)); - } - } - - @Override - public void documentChanged(DocumentEvent e) { - if (!myTaskFile.isTrackChanges()) { - return; - } - if (myAnswerPlaceholders.isEmpty()) return; - if (e instanceof DocumentEventImpl) { - DocumentEventImpl event = (DocumentEventImpl)e; - Document document = e.getDocument(); - int offset = e.getOffset(); - int change = event.getNewLength() - event.getOldLength(); - for (AnswerPlaceholderWrapper answerPlaceholderWrapper : myAnswerPlaceholders) { - int twStart = answerPlaceholderWrapper.getTwStart(); - if (twStart > offset) { - twStart += change; - } - int twEnd = answerPlaceholderWrapper.getTwEnd(); - if (twEnd >= offset) { - twEnd += change; - } - AnswerPlaceholder answerPlaceholder = answerPlaceholderWrapper.getAnswerPlaceholder(); - int length = twEnd - twStart; - answerPlaceholder.setOffset(twStart); - if (myTrackLength) { - if (answerPlaceholder.getUseLength()) { - answerPlaceholder.setLength(length); - } else { - if (answerPlaceholder.isActive() && myTaskFile.isTrackLengths()) { - answerPlaceholder.setPossibleAnswer(document.getText(TextRange.create(twStart, twStart + length))); - } - } - } - } - } - } - - private static class AnswerPlaceholderWrapper { - public AnswerPlaceholder myAnswerPlaceholder; - public int myTwStart; - public int myTwEnd; - - public AnswerPlaceholderWrapper(AnswerPlaceholder answerPlaceholder, int twStart, int twEnd) { - myAnswerPlaceholder = answerPlaceholder; - myTwStart = twStart; - myTwEnd = twEnd; - } - - public int getTwStart() { - return myTwStart; - } - - public int getTwEnd() { - return myTwEnd; - } - - public AnswerPlaceholder getAnswerPlaceholder() { - return myAnswerPlaceholder; - } - } -} - diff --git a/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java b/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java deleted file mode 100644 index eedd5760fd07..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/core/EduNames.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2000-2014 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.core; - -import com.intellij.openapi.util.text.StringUtil; -import org.jetbrains.annotations.NonNls; - -@NonNls -public class EduNames { - public static final String HINTS = "hints"; - public static final String LESSON = "lesson"; - public static final String LESSON_TITLED = StringUtil.toTitleCase(LESSON); - public static final String TASK = "task"; - public static final String TASK_TITLED = StringUtil.toTitleCase(TASK); - public static final String COURSE = "course"; - public static final String TEST_TAB_NAME = "test"; - public static final String USER_TEST_INPUT = "input"; - public static final String USER_TEST_OUTPUT = "output"; - public static final String WINDOW_POSTFIX = "_window."; - public static final String WINDOWS_POSTFIX = "_windows"; - public static final String ANSWERS_POSTFIX = "_answers"; - public static final String USER_TESTS = "userTests"; - public static final String TEST_HELPER = "test_helper.py"; - - public static final String COURSE_META_FILE = "course.json"; - public static final String PYCHARM_ADDITIONAL = "PyCharm additional materials"; - public static final String PYCHARM = "PyCharm"; - - public static final String STUDY = "Study"; - public static final String ADAPTIVE = "Adaptive"; - - public static final String ANSWER_PLACEHOLDER = "Answer Placeholder"; - public static final String SRC = "src"; - - public static final String SUBTASK_MARKER = "_subtask"; - public static final String SUBTASK = "subtask"; - - public static final String STUDY_PROJECT_XML_PATH = "/.idea/study_project.xml"; - - private EduNames() { - } - -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/core/EduUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/core/EduUtils.java deleted file mode 100644 index 7d5aded30d26..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/core/EduUtils.java +++ /dev/null @@ -1,271 +0,0 @@ -package com.jetbrains.edu.learning.core; - -import com.intellij.ide.SaveAndSyncHandler; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.Result; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.command.UndoConfirmationPolicy; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.command.undo.UndoManager; -import com.intellij.openapi.command.undo.UndoableAction; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileTypes.PlainTextFileType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.psi.PsiDirectory; -import com.intellij.testFramework.LightVirtualFile; -import com.intellij.util.containers.ContainerUtil; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.*; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.apache.commons.codec.binary.Base64; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.imageio.ImageIO; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Map; - -public class EduUtils { - private EduUtils() { - } - - private static final Logger LOG = Logger.getInstance(EduUtils.class.getName()); - - public static final Comparator INDEX_COMPARATOR = Comparator.comparingInt(StudyItem::getIndex); - - public static void enableAction(@NotNull final AnActionEvent event, boolean isEnable) { - final Presentation presentation = event.getPresentation(); - presentation.setVisible(isEnable); - presentation.setEnabled(isEnable); - } - - /** - * Gets number index in directory names like "task1", "lesson2" - * - * @param fullName full name of directory - * @param logicalName part of name without index - * @return index of object - */ - public static int getIndex(@NotNull final String fullName, @NotNull final String logicalName) { - if (!fullName.startsWith(logicalName)) { - return -1; - } - try { - return Integer.parseInt(fullName.substring(logicalName.length())) - 1; - } - catch (NumberFormatException e) { - return -1; - } - } - - public static boolean indexIsValid(int index, Collection collection) { - int size = collection.size(); - return index >= 0 && index < size; - } - - @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - @Nullable - public static VirtualFile flushWindows(@NotNull final TaskFile taskFile, @NotNull final VirtualFile file) { - final VirtualFile taskDir = file.getParent(); - VirtualFile fileWindows = null; - final Document document = FileDocumentManager.getInstance().getDocument(file); - if (document == null) { - LOG.debug("Couldn't flush windows"); - return null; - } - if (taskDir != null) { - final String name = file.getNameWithoutExtension() + EduNames.WINDOWS_POSTFIX; - deleteWindowsFile(taskDir, name); - PrintWriter printWriter = null; - try { - fileWindows = taskDir.createChildData(taskFile, name); - printWriter = new PrintWriter(new FileOutputStream(fileWindows.getPath())); - for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) { - int length = answerPlaceholder.getRealLength(); - int start = answerPlaceholder.getOffset(); - final String windowDescription = document.getText(new TextRange(start, start + length)); - printWriter.println("#educational_plugin_window = " + windowDescription); - } - ApplicationManager.getApplication().runWriteAction(() -> FileDocumentManager.getInstance().saveDocument(document)); - } - catch (IOException e) { - LOG.error(e); - } - finally { - if (printWriter != null) { - printWriter.close(); - } - synchronize(); - } - } - return fileWindows; - } - - public static void synchronize() { - FileDocumentManager.getInstance().saveAllDocuments(); - SaveAndSyncHandler.getInstance().refreshOpenFiles(); - VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); - } - - @Nullable - public static TaskFile createStudentFile(Project project, VirtualFile answerFile, @Nullable Task task, int targetSubtaskIndex) { - try { - if (task == null) { - task = StudyUtils.getTaskForFile(project, answerFile); - if (task == null) { - return null; - } - task = task.copy(); - } - TaskFile taskFile = task.getTaskFile(StudyUtils.pathRelativeToTask(answerFile)); - if (taskFile == null) { - return null; - } - if (isImage(taskFile.name)) { - taskFile.text = Base64.encodeBase64String(answerFile.contentsToByteArray()); - return taskFile; - } - Document document = FileDocumentManager.getInstance().getDocument(answerFile); - if (document == null) { - return null; - } - FileDocumentManager.getInstance().saveDocument(document); - final LightVirtualFile studentFile = new LightVirtualFile("student_task", PlainTextFileType.INSTANCE, document.getText()); - Document studentDocument = FileDocumentManager.getInstance().getDocument(studentFile); - if (studentDocument == null) { - return null; - } - EduDocumentListener listener = new EduDocumentListener(taskFile, false); - studentDocument.addDocumentListener(listener); - taskFile.setTrackLengths(false); - for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { - if (task instanceof TaskWithSubtasks) { - int fromSubtask = ((TaskWithSubtasks)task).getActiveSubtaskIndex(); - placeholder.switchSubtask(studentDocument, fromSubtask, targetSubtaskIndex); - } - } - for (AnswerPlaceholder placeholder : taskFile.getAnswerPlaceholders()) { - replaceWithTaskText(studentDocument, placeholder, targetSubtaskIndex); - } - taskFile.setTrackChanges(true); - studentDocument.removeDocumentListener(listener); - taskFile.text = studentDocument.getImmutableCharSequence().toString(); - return taskFile; - } - catch (IOException e) { - LOG.error("Failed to convert answer file to student one"); - } - - return null; - } - - private static void replaceWithTaskText(Document studentDocument, AnswerPlaceholder placeholder, int toSubtaskIndex) { - AnswerPlaceholderSubtaskInfo info = placeholder.getSubtaskInfos().get(toSubtaskIndex); - if (info == null) { - return; - } - String replacementText; - if (Collections.min(placeholder.getSubtaskInfos().keySet()) == toSubtaskIndex) { - replacementText = info.getPlaceholderText(); - } - else { - Integer max = Collections.max(ContainerUtil.filter(placeholder.getSubtaskInfos().keySet(), i -> i < toSubtaskIndex)); - replacementText = placeholder.getSubtaskInfos().get(max).getPossibleAnswer(); - } - replaceAnswerPlaceholder(studentDocument, placeholder, placeholder.getVisibleLength(toSubtaskIndex), replacementText); - } - - public static void replaceAnswerPlaceholder(@NotNull final Document document, - @NotNull final AnswerPlaceholder answerPlaceholder, - int length, - String replacementText) { - final int offset = answerPlaceholder.getOffset(); - CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> { - document.replaceString(offset, offset + length, replacementText); - FileDocumentManager.getInstance().saveDocument(document); - })); - } - - public static void deleteWindowDescriptions(@NotNull final Task task, @NotNull final VirtualFile taskDir) { - for (Map.Entry entry : task.getTaskFiles().entrySet()) { - String name = entry.getKey(); - VirtualFile virtualFile = taskDir.findFileByRelativePath(name); - if (virtualFile == null) { - continue; - } - String windowsFileName = virtualFile.getNameWithoutExtension() + EduNames.WINDOWS_POSTFIX; - VirtualFile parentDir = virtualFile.getParent(); - deleteWindowsFile(parentDir, windowsFileName); - } - } - - private static void deleteWindowsFile(@NotNull final VirtualFile taskDir, @NotNull final String name) { - final VirtualFile fileWindows = taskDir.findChild(name); - if (fileWindows != null && fileWindows.exists()) { - ApplicationManager.getApplication().runWriteAction(() -> { - try { - fileWindows.delete(taskDir); - } - catch (IOException e) { - LOG.warn("Tried to delete non existed _windows file"); - } - }); - } - } - - @Nullable - public static Task getTask(@NotNull final PsiDirectory directory, @NotNull final Course course) { - PsiDirectory lessonDir = directory.getParent(); - if (lessonDir == null) { - return null; - } - Lesson lesson = course.getLesson(lessonDir.getName()); - if (lesson == null) { - return null; - } - return lesson.getTask(directory.getName()); - } - - public static boolean isImage(String fileName) { - final String[] readerFormatNames = ImageIO.getReaderFormatNames(); - for (@NonNls String format : readerFormatNames) { - final String ext = format.toLowerCase(); - if (fileName.endsWith(ext)) { - return true; - } - } - return false; - } - - public static void runUndoableAction(Project project, String name, UndoableAction action) { - runUndoableAction(project, name, action, UndoConfirmationPolicy.DO_NOT_REQUEST_CONFIRMATION); - } - - public static void runUndoableAction(Project project, String name, UndoableAction action, UndoConfirmationPolicy confirmationPolicy) { - new WriteCommandAction(project, name) { - protected void run(@NotNull final Result result) throws Throwable { - action.redo(); - UndoManager.getInstance(project).undoableActionPerformed(action); - } - - @Override - protected UndoConfirmationPolicy getUndoConfirmationPolicy() { - return confirmationPolicy; - } - }.execute(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java deleted file mode 100644 index a28fccc98338..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholder.java +++ /dev/null @@ -1,301 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat; - -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import com.intellij.openapi.editor.Document; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.hash.HashMap; -import com.intellij.util.xmlb.annotations.Transient; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Implementation of windows which user should type in - */ - -public class AnswerPlaceholder { - - @SerializedName("offset") - @Expose private int myOffset = -1; - - @SerializedName("length") - @Expose private int myLength = -1; - - private int myIndex = -1; - private MyInitialState myInitialState; - private boolean mySelected = false; - private boolean myUseLength = true; - - @Transient private TaskFile myTaskFile; - - @SerializedName("subtask_infos") - @Expose private Map mySubtaskInfos = new HashMap<>(); - public AnswerPlaceholder() { - } - - public void initAnswerPlaceholder(final TaskFile file, boolean isRestarted) { - setTaskFile(file); - if (!isRestarted) { - setInitialState(new MyInitialState(myOffset, myLength)); - for (AnswerPlaceholderSubtaskInfo info : getSubtaskInfos().values()) { - info.setStatus(file.getTask().getStatus()); - } - } - } - - public int getIndex() { - return myIndex; - } - - public void setIndex(int index) { - myIndex = index; - } - - /** - * in actions {@link AnswerPlaceholder#getRealLength()} should be used - */ - public int getLength() { - return myLength; - } - - public void setLength(int length) { - myLength = length; - } - - @Transient - public String getPossibleAnswer() { - return getActiveSubtaskInfo().getPossibleAnswer(); - } - - @Transient - public void setPossibleAnswer(String possibleAnswer) { - getActiveSubtaskInfo().setPossibleAnswer(possibleAnswer); - } - - public MyInitialState getInitialState() { - return myInitialState; - } - - public void setInitialState(MyInitialState initialState) { - myInitialState = initialState; - } - - @Transient - public String getTaskText() { - return getActiveSubtaskInfo().getPlaceholderText(); - } - - @Transient - public void setTaskText(String taskText) { - getActiveSubtaskInfo().setPlaceholderText(taskText); - } - - @Transient - public TaskFile getTaskFile() { - return myTaskFile; - } - - @Transient - public void setTaskFile(TaskFile taskFile) { - myTaskFile = taskFile; - } - - public int getPossibleAnswerLength() { - return getPossibleAnswer().length(); - } - - /** - * Returns window to its initial state - */ - public void reset() { - myOffset = myInitialState.getOffset(); - myLength = myInitialState.getLength(); - } - - @Transient - public StudyStatus getStatus() { - AnswerPlaceholderSubtaskInfo info = getActiveSubtaskInfo(); - return info != null ? info.getStatus() : StudyStatus.Unchecked; - } - - @Transient - public void setStatus(StudyStatus status) { - getActiveSubtaskInfo().setStatus(status); - } - - public boolean getSelected() { - return mySelected; - } - - public void setSelected(boolean selected) { - mySelected = selected; - } - - public void init() { - setInitialState(new MyInitialState(myOffset, getTaskText().length())); - } - - public boolean getUseLength() { - return myUseLength; - } - - /** - * @return length or possible answer length - */ - public int getRealLength() { - return myUseLength ? getLength() : getVisibleLength(getActiveSubtaskIndex()); - } - - public void setUseLength(boolean useLength) { - myUseLength = useLength; - } - - public int getOffset() { - return myOffset; - } - - public void setOffset(int offset) { - myOffset = offset; - } - - @Transient - public List getHints() { - return getActiveSubtaskInfo().getHints(); - } - - @Transient - public void setHints(@NotNull final List hints) { - getActiveSubtaskInfo().setHints(hints); - } - - public void addHint(@NotNull final String text) { - getActiveSubtaskInfo().addHint(text); - } - - public void removeHint(int i) { - getActiveSubtaskInfo().removeHint(i); - } - - public Map getSubtaskInfos() { - return mySubtaskInfos; - } - - public void setSubtaskInfos(Map subtaskInfos) { - mySubtaskInfos = subtaskInfos; - } - - public boolean isActive() { - return getActiveSubtaskInfo() != null; - } - - public static class MyInitialState { - private int length = -1; - private int offset = -1; - - public MyInitialState() { - } - - public MyInitialState(int initialOffset, int length) { - this.offset = initialOffset; - this.length = length; - } - - public int getLength() { - return length; - } - - public void setLength(int length) { - this.length = length; - } - - public int getOffset() { - return offset; - } - - public void setOffset(int offset) { - this.offset = offset; - } - } - - public AnswerPlaceholderSubtaskInfo getActiveSubtaskInfo() { - return mySubtaskInfos.get(getActiveSubtaskIndex()); - } - - public int getActiveSubtaskIndex() { - if (myTaskFile == null || myTaskFile.getTask() == null) { - return 0; - } - final Task task = myTaskFile.getTask(); - return task instanceof TaskWithSubtasks ? ((TaskWithSubtasks)task).getActiveSubtaskIndex() : 0; - } - - public int getVisibleLength(int subtaskIndex) { - int minIndex = Collections.min(mySubtaskInfos.keySet()); - AnswerPlaceholderSubtaskInfo minInfo = mySubtaskInfos.get(minIndex); - if (minIndex == subtaskIndex) { - return getUseLength() ? myLength : minInfo.getPossibleAnswer().length(); - } - if (minIndex > subtaskIndex) { - return minInfo.isNeedInsertText() ? 0 : minInfo.getPlaceholderText().length(); - } - int maxIndex = Collections.max(ContainerUtil.filter(mySubtaskInfos.keySet(), i -> i <= subtaskIndex)); - return getUseLength() ? myLength : mySubtaskInfos.get(maxIndex).getPossibleAnswer().length(); - } - - public void switchSubtask(@NotNull Document document, int fromSubtask, int toSubtask) { - Set indexes = mySubtaskInfos.keySet(); - int visibleLength = getVisibleLength(fromSubtask); - if (indexes.contains(toSubtask)) { - if (!myUseLength) { - String replacementText = mySubtaskInfos.get(toSubtask).getPossibleAnswer(); - EduUtils.replaceAnswerPlaceholder(document, this, visibleLength, replacementText); - return; - } - } - Integer minIndex = Collections.min(indexes); - if (fromSubtask < toSubtask) { - if (minIndex > fromSubtask && minIndex <= toSubtask) { - Integer maxIndex = Collections.max(ContainerUtil.filter(indexes, integer -> integer <= toSubtask)); - AnswerPlaceholderSubtaskInfo maxInfo = mySubtaskInfos.get(maxIndex); - String replacementText = myUseLength ? maxInfo.getPlaceholderText() : maxInfo.getPossibleAnswer(); - EduUtils.replaceAnswerPlaceholder(document, this, visibleLength, replacementText); - return; - } - } - if (fromSubtask > toSubtask) { - if (minIndex > toSubtask && minIndex <= fromSubtask) { - AnswerPlaceholderSubtaskInfo minInfo = mySubtaskInfos.get(minIndex); - if (minInfo.isNeedInsertText()) { - EduUtils.replaceAnswerPlaceholder(document, this, visibleLength, ""); - } - else { - String replacementText = minInfo.getPlaceholderText(); - EduUtils.replaceAnswerPlaceholder(document, this, visibleLength, replacementText); - } - return; - } - if (indexes.contains(fromSubtask)) { - List filtered = ContainerUtil.filter(indexes, index -> index < fromSubtask); - if (filtered.isEmpty()) { - return; - } - Integer maxIndex = Collections.max(filtered); - AnswerPlaceholderSubtaskInfo maxInfo = mySubtaskInfos.get(maxIndex); - if (maxInfo.isNeedInsertText()) { - EduUtils.replaceAnswerPlaceholder(document, this, visibleLength, ""); - } - else { - String replacementText = myUseLength ? maxInfo.getPlaceholderText() : maxInfo.getPossibleAnswer(); - EduUtils.replaceAnswerPlaceholder(document, this, visibleLength, replacementText); - } - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderComparator.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderComparator.java deleted file mode 100644 index 90cc4d8f8ba7..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderComparator.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat; - -import java.util.Comparator; - -public class AnswerPlaceholderComparator implements Comparator { - @Override - public int compare(AnswerPlaceholder o1, AnswerPlaceholder answerPlaceholder) { - return o1.getOffset() - answerPlaceholder.getOffset(); - } - - @Override - public boolean equals(Object obj) { - return false; - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderSubtaskInfo.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderSubtaskInfo.java deleted file mode 100644 index 4cd5d7aca24b..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/AnswerPlaceholderSubtaskInfo.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat; - -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import com.intellij.openapi.diagnostic.Logger; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class AnswerPlaceholderSubtaskInfo { - private static final Logger LOG = Logger.getInstance(AnswerPlaceholderSubtaskInfo.class); - - @SerializedName("hints") - @Expose private List myHints = new ArrayList<>(); - - @SerializedName("possible_answer") - @Expose private String myPossibleAnswer = ""; - - @SerializedName("placeholder_text") - @Expose private String myPlaceholderText; - - // actual student's answer, used to reset file to current subtask initial state - private String myAnswer = ""; - private boolean mySelected = false; - private StudyStatus myStatus = StudyStatus.Unchecked; - - @SerializedName("has_frame") - @Expose private boolean myHasFrame = true; - - @SerializedName("need_insert_text") - @Expose private boolean myNeedInsertText = false; - - public StudyStatus getStatus() { - return myStatus; - } - - public void setStatus(StudyStatus status) { - myStatus = status; - } - - public void addHint(@NotNull final String text) { - myHints.add(text); - } - - public void removeHint(int i) { - if (i < myHints.size()) { - myHints.remove(i); - } - else { - LOG.warn("Trying to remove nonexistent hint. Hint to remove number: " + i + " number of hints: " + myHints.size()); - } - } - - public List getHints() { - return myHints; - } - - public void setHints(List hints) { - myHints = hints; - } - - public String getPossibleAnswer() { - return myPossibleAnswer; - } - - public void setPossibleAnswer(String possibleAnswer) { - myPossibleAnswer = possibleAnswer; - } - - public String getPlaceholderText() { - return myPlaceholderText; - } - - public void setPlaceholderText(String placeholderText) { - myPlaceholderText = placeholderText; - } - - public boolean getSelected() { - return mySelected; - } - - public void setSelected(boolean selected) { - mySelected = selected; - } - - public boolean isHasFrame() { - return myHasFrame; - } - - public void setHasFrame(boolean hasFrame) { - myHasFrame = hasFrame; - } - - public boolean isNeedInsertText() { - return myNeedInsertText; - } - - public void setNeedInsertText(boolean needInsertText) { - myNeedInsertText = needInsertText; - } - - public String getAnswer() { - return myAnswer; - } - - public void setAnswer(String answer) { - myAnswer = answer; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Course.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Course.java deleted file mode 100644 index 7e9bdea7c01e..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Course.java +++ /dev/null @@ -1,217 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat; - -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import com.intellij.lang.Language; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.xmlb.XmlSerializer; -import com.intellij.util.xmlb.annotations.Transient; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.stepic.StepicUser; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -public class Course { - public static final Course INVALID_COURSE = new Course(); - - @Expose protected List lessons = new ArrayList<>(); - transient private List authors = new ArrayList<>(); - @Expose @SerializedName("summary") private String description; - @Expose @SerializedName("title") private String name; - - @Expose @SerializedName("programming_language") private String myLanguage = "Python"; - - //this field is used to distinguish ordinary and CheckIO projects, - //"PyCharm" is used here for historical reasons - private String courseType = EduNames.PYCHARM; - protected String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes - - public Course() {} - - /** - * Initializes state of course - */ - public void initCourse(boolean isRestarted) { - for (Lesson lesson : getLessons()) { - lesson.initLesson(this, isRestarted); - } - } - - /** - * exclude service lesson containing additional files for the course. Returns lessons copy. - */ - public List getLessons() { - return getLessons(false); - } - - /** - * returns service lesson as well. Meant to be used in project generation/serialization - */ - public List getLessons(boolean withAdditional) { - return withAdditional ? lessons : lessons.stream().filter(lesson -> !EduNames.PYCHARM_ADDITIONAL.equals(lesson.getName())) - .collect(Collectors.toList()); - } - - public void setLessons(List lessons) { - this.lessons = lessons; - } - - public void addLessons(List lessons) { - this.lessons.addAll(lessons); - } - - public void addLesson(@NotNull final Lesson lesson) { - lessons.add(lesson); - } - - public void removeLesson(Lesson lesson) { - lessons.remove(lesson); - } - - public Lesson getLesson(@NotNull final String name) { - int lessonIndex = EduUtils.getIndex(name, EduNames.LESSON); - if (!EduUtils.indexIsValid(lessonIndex, lessons)) { - return null; - } - for (Lesson lesson : lessons) { - if (lesson.getIndex() - 1 == lessonIndex) { - return lesson; - } - } - return null; - } - - public Lesson getLesson(int stepicId) { - for (Lesson lesson : lessons) { - if (lesson.getId() == stepicId) { - return lesson; - } - } - return null; - } - - @NotNull - public List getAuthors() { - return authors; - } - - public static String getAuthorsString(@NotNull List authors) { - return StringUtil.join(authors, StepicUser::getName, ", "); - } - - @Transient - public void setAuthorsAsString(String[] authors) { - this.authors = new ArrayList<>(); - for (String name : authors) { - final List firstLast = StringUtil.split(name, " "); - if (!firstLast.isEmpty()) { - final StepicUser stepicUser = StepicUser.createEmptyUser(); - stepicUser.setFirstName(firstLast.remove(0)); - if (firstLast.size() > 0) { - stepicUser.setLastName(StringUtil.join(firstLast, " ")); - } - this.authors.add(stepicUser); - } - } - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public boolean isUpToDate() { - return true; - } - - public void setUpdated() {} - - public Language getLanguageById() { - return Language.findLanguageByID(getLanguageID()); - } - - /** - * This method should be used by serialized only - * Use {@link #getLanguageID()} and {@link #getLanguageVersion()} methods instead - */ - @Deprecated - public String getLanguage() { - return myLanguage; - } - - public void setLanguage(@NotNull final String language) { - myLanguage = language; - } - - public String getLanguageID() { - return myLanguage.split(" ")[0]; - } - - @Nullable - public String getLanguageVersion() { - String[] split = myLanguage.split(" "); - if (split.length <= 1) { - return null; - } - return split[1]; - } - - public void setAuthors(List authors) { - this.authors = authors; - } - - @NotNull - public String getCourseType() { - return courseType; - } - - public void setCourseType(String courseType) { - this.courseType = courseType; - } - - public String getCourseMode() { - return courseMode; - } - - public void setCourseMode(String courseMode) { - this.courseMode = courseMode; - } - - public Course copy() { - Element element = XmlSerializer.serialize(this); - Course copy = XmlSerializer.deserialize(element, getClass()); - copy.initCourse(true); - return copy; - } - - public boolean isAdaptive() { - return false; - } - - public void sortLessons() { - Collections.sort(lessons, EduUtils.INDEX_COMPARATOR); - } - - @Override - public String toString() { - return getName(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Lesson.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Lesson.java deleted file mode 100644 index 7710dfc7eb7f..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/Lesson.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat; - -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import com.intellij.util.xmlb.annotations.AbstractCollection; -import com.intellij.util.xmlb.annotations.Transient; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.tasks.*; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class Lesson implements StudyItem { - @Expose @SerializedName("id") private int myId; - @Transient public List steps; - @Transient public List tags; - @Transient boolean is_public; - @Expose @SerializedName("update_date") private Date myUpdateDate; - - @Expose - @SerializedName("title") - private String name; - - @Expose - @SerializedName("task_list") - @AbstractCollection(elementTypes = { - PyCharmTask.class, - ChoiceTask.class, - TheoryTask.class, - CodeTask.class, - TaskWithSubtasks.class, - OutputTask.class - }) - public List taskList = new ArrayList<>(); - - @Transient - private Course myCourse = null; - - // index is visible to user number of lesson from 1 to lesson number - private int myIndex = -1; - - public Lesson() { - } - - public void initLesson(final Course course, boolean isRestarted) { - setCourse(course); - for (Task task : getTaskList()) { - task.initTask(this, isRestarted); - } - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getIndex() { - return myIndex; - } - - public void setIndex(int index) { - myIndex = index; - } - - public List getTaskList() { - return taskList; - } - - @Transient - public Course getCourse() { - return myCourse; - } - - @Transient - public void setCourse(Course course) { - myCourse = course; - } - - public void addTask(@NotNull final Task task) { - taskList.add(task); - } - - public Task getTask(@NotNull final String name) { - int index = EduUtils.getIndex(name, EduNames.TASK); - List tasks = getTaskList(); - if (!EduUtils.indexIsValid(index, tasks)) { - return null; - } - for (Task task : tasks) { - if (task.getIndex() - 1 == index) { - return task; - } - } - return null; - } - - public Task getTask(int id) { - for (Task task : taskList) { - if (task.getStepId() == id) { - return task; - } - } - return null; - } - - public void updateTaskList(List taskList) { - this.taskList = taskList; - } - - public StudyStatus getStatus() { - for (Task task : taskList) { - if (task.getStatus() != StudyStatus.Solved) { - return StudyStatus.Unchecked; - } - } - return StudyStatus.Solved; - } - - public int getId() { - return myId; - } - - public void setId(int id) { - this.myId = id; - } - - public Date getUpdateDate() { - return myUpdateDate; - } - - public void setUpdateDate(Date updateDate) { - myUpdateDate = updateDate; - } - - public void setPublic(boolean isPublic) { - this.is_public = isPublic; - } - - public boolean isUpToDate() { - if (myId == 0) return true; - final Date date = EduStepicConnector.getLessonUpdateDate(myId); - if (date == null) return true; - if (myUpdateDate == null) return false; - for (Task task : taskList) { - if (!task.isUpToDate()) return false; - } - return !date.after(myUpdateDate); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/RemoteCourse.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/RemoteCourse.java deleted file mode 100644 index 012fc54ffd79..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/RemoteCourse.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat; - -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import com.intellij.openapi.progress.EmptyProgressIndicator; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task.Backgroundable; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.EduStepicNames; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class RemoteCourse extends Course { - //course type in format "pycharm " - @SerializedName("course_format") private String myType = - String.format("%s%d %s", EduStepicNames.PYCHARM_PREFIX, EduStepicConnector.CURRENT_VERSION, getLanguageID()); - @SerializedName("is_idea_compatible") private boolean isCompatible = true; - List sections; - List instructors = new ArrayList<>(); - @Expose private int id; - @Expose @SerializedName("update_date") private Date myUpdateDate; - private Boolean isUpToDate = true; - @Expose private boolean isAdaptive = false; - @Expose @SerializedName("is_public") boolean isPublic; - - public String getType() { - return myType; - } - - public void setLanguage(@NotNull final String language) { - super.setLanguage(language); - updateType(language); - } - - public List getSections() { - return sections; - } - - public void setSections(List sections) { - this.sections = sections; - } - - public void setInstructors(List instructors) { - this.instructors = instructors; - } - - public List getInstructors() { - return instructors; - } - - - public boolean isUpToDate() { - if (id == 0) return true; - if (!EduNames.STUDY.equals(courseMode)) return true; - - ProgressManager.getInstance().runProcessWithProgressAsynchronously(new Backgroundable(null, "Updating Course") { - @Override - public void run(@NotNull ProgressIndicator indicator) { - final Date date = EduStepicConnector.getCourseUpdateDate(id); - if (date == null) return; - if (date.after(myUpdateDate)) { - isUpToDate = false; - } - for (Lesson lesson : lessons) { - if (!lesson.isUpToDate()) { - isUpToDate = false; - } - } - } - }, new EmptyProgressIndicator()); - - return isUpToDate; - } - - public void setUpdated() { - setUpdateDate(EduStepicConnector.getCourseUpdateDate(id)); - for (Lesson lesson : lessons) { - lesson.setUpdateDate(EduStepicConnector.getLessonUpdateDate(lesson.getId())); - for (Task task : lesson.getTaskList()) { - task.setUpdateDate(EduStepicConnector.getTaskUpdateDate(task.getStepId())); - } - } - } - - public void setUpdateDate(Date date) { - myUpdateDate = date; - } - - public Date getUpdateDate() { - return myUpdateDate; - } - - 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 void copyCourseParameters(RemoteCourse course) { - setName(course.getName()); - setUpdateDate(course.getUpdateDate()); - } - - private void updateType(String language) { - myType = String.format("%s%d %s", EduStepicNames.PYCHARM_PREFIX, EduStepicConnector.CURRENT_VERSION, language); - } - - public boolean isPublic() { - return isPublic; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/StudyItem.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/StudyItem.java deleted file mode 100644 index 682dbb0a1bfe..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/StudyItem.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat; - -public interface StudyItem { - String getName(); - void setName(String name); - int getIndex(); - void setIndex(int index); -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/StudyStatus.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/StudyStatus.java deleted file mode 100644 index b4e1b97587bc..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/StudyStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat; - -public enum StudyStatus { - Unchecked, Solved, Failed -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java deleted file mode 100644 index cf66a07fe3ec..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat; - -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import com.intellij.util.xmlb.annotations.Transient; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -/** - * Implementation of task file which contains task windows for student to type in and - * which is visible to student in project view - */ - -public class TaskFile { - @Expose public String name; - @Expose public String text; - private int myIndex = -1; - private boolean myUserCreated = false; - private boolean myTrackChanges = true; - private boolean myTrackLengths = true; - private boolean myHighlightErrors = false; - @Expose @SerializedName("placeholders") private List myAnswerPlaceholders = new ArrayList<>(); - @Transient private Task myTask; - - public TaskFile() { - } - - public void initTaskFile(final Task task, boolean isRestarted) { - setTask(task); - final List answerPlaceholders = getAnswerPlaceholders(); - for (AnswerPlaceholder answerPlaceholder : answerPlaceholders) { - answerPlaceholder.initAnswerPlaceholder(this, isRestarted); - } - answerPlaceholders.sort(new AnswerPlaceholderComparator()); - for (int i = 0; i < answerPlaceholders.size(); i++) { - answerPlaceholders.get(i).setIndex(i); - } - } - - public List getAnswerPlaceholders() { - return myAnswerPlaceholders; - } - - public List getActivePlaceholders() { - List result = new ArrayList<>(); - for (AnswerPlaceholder placeholder : myAnswerPlaceholders) { - if (placeholder.getActiveSubtaskInfo() != null) { - result.add(placeholder); - } - } - return result; - } - - public void setAnswerPlaceholders(List answerPlaceholders) { - this.myAnswerPlaceholders = answerPlaceholders; - } - - public void addAnswerPlaceholder(AnswerPlaceholder answerPlaceholder) { - myAnswerPlaceholders.add(answerPlaceholder); - } - - public int getIndex() { - return myIndex; - } - - public void setIndex(int index) { - myIndex = index; - } - - @Transient - public Task getTask() { - return myTask; - } - - @Transient - public void setTask(Task task) { - myTask = task; - } - - /** - * @param offset position in editor - * @return answer placeholder located in specified position or null if there is no task window in this position - */ - @Nullable - public AnswerPlaceholder getAnswerPlaceholder(int offset) { - return StudyUtils.getAnswerPlaceholder(offset, getActivePlaceholders()); - } - - public boolean isTrackLengths() { - return myTrackLengths; - } - - public void setTrackLengths(boolean trackLengths) { - myTrackLengths = trackLengths; - } - - public static void copy(@NotNull final TaskFile source, @NotNull final TaskFile target) { - List sourceAnswerPlaceholders = source.getActivePlaceholders(); - List answerPlaceholdersCopy = new ArrayList<>(sourceAnswerPlaceholders.size()); - for (AnswerPlaceholder answerPlaceholder : sourceAnswerPlaceholders) { - AnswerPlaceholder answerPlaceholderCopy = new AnswerPlaceholder(); - answerPlaceholderCopy.setTaskText(answerPlaceholder.getTaskText()); - answerPlaceholderCopy.setOffset(answerPlaceholder.getOffset()); - answerPlaceholderCopy.setLength(answerPlaceholder.getLength()); - answerPlaceholderCopy.setPossibleAnswer(answerPlaceholder.getPossibleAnswer()); - answerPlaceholderCopy.setIndex(answerPlaceholder.getIndex()); - answerPlaceholderCopy.setHints(answerPlaceholder.getHints()); - final AnswerPlaceholder.MyInitialState state = answerPlaceholder.getInitialState(); - if (state != null) { - answerPlaceholderCopy.setInitialState(new AnswerPlaceholder.MyInitialState(state.getOffset(), state.getLength())); - } - answerPlaceholdersCopy.add(answerPlaceholderCopy); - } - target.name = source.name; - target.setAnswerPlaceholders(answerPlaceholdersCopy); - } - - public void setUserCreated(boolean userCreated) { - myUserCreated = userCreated; - } - - public boolean isUserCreated() { - return myUserCreated; - } - - public boolean isTrackChanges() { - return myTrackChanges; - } - - public void setTrackChanges(boolean trackChanges) { - myTrackChanges = trackChanges; - } - - public boolean isHighlightErrors() { - return myHighlightErrors; - } - - public void setHighlightErrors(boolean highlightErrors) { - myHighlightErrors = highlightErrors; - } - - public void sortAnswerPlaceholders() { - myAnswerPlaceholders.sort(new AnswerPlaceholderComparator()); - for (int i = 0; i < myAnswerPlaceholders.size(); i++) { - myAnswerPlaceholders.get(i).setIndex(i); - } - } - - public boolean hasFailedPlaceholders() { - for (AnswerPlaceholder placeholder : myAnswerPlaceholders) { - if (placeholder.getStatus() == StudyStatus.Failed) { - return true; - } - } - return false; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/UserTest.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/UserTest.java deleted file mode 100644 index dd5546035761..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/UserTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat; - -public class UserTest { - private String input; - private String output; - private StringBuilder myInputBuffer = new StringBuilder(); - private StringBuilder myOutputBuffer = new StringBuilder(); - private boolean myEditable = false; - - public String getInput() { - return input; - } - - public void setInput(String input) { - this.input = input; - } - - public String getOutput() { - return output; - } - - public void setOutput(String output) { - this.output = output; - } - - public StringBuilder getInputBuffer() { - return myInputBuffer; - } - - public StringBuilder getOutputBuffer() { - return myOutputBuffer; - } - - public boolean isEditable() { - return myEditable; - } - - public void setEditable(boolean editable) { - myEditable = editable; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/ChoiceTask.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/ChoiceTask.java deleted file mode 100644 index 553ecb6a9a00..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/ChoiceTask.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat.tasks; - -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.actions.StudyCheckAction; -import com.jetbrains.edu.learning.checker.StudyCheckResult; -import com.jetbrains.edu.learning.checker.StudyTaskChecker; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.editor.StudyChoiceVariantsPanel; -import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicUser; -import com.jetbrains.edu.learning.ui.StudyToolWindow; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -public class ChoiceTask extends Task { - - @SuppressWarnings("unused") //used for deserialization - public ChoiceTask() {} - - @Expose @SerializedName("choice_variants") private List myChoiceVariants = new ArrayList<>(); - @Expose @SerializedName("is_multichoice") private boolean myIsMultipleChoice; - @SerializedName("selected_variants") private List mySelectedVariants = new ArrayList<>(); - - public List getSelectedVariants() { - return mySelectedVariants; - } - - public void setSelectedVariants(List selectedVariants) { - mySelectedVariants = selectedVariants; - } - - public boolean isMultipleChoice() { - return myIsMultipleChoice; - } - - public void setMultipleChoice(boolean multipleChoice) { - myIsMultipleChoice = multipleChoice; - } - - public List getChoiceVariants() { - return myChoiceVariants; - } - - public void setChoiceVariants(List choiceVariants) { - myChoiceVariants = choiceVariants; - } - - public ChoiceTask(@NotNull final String name) { - super(name); - } - - @Override - public String getTaskType() { - return "choice"; - } - - @Override - public StudyTaskChecker getChecker(@NotNull Project project) { - return new StudyTaskChecker(this, project) { - @Override - public StudyCheckResult checkOnRemote(@Nullable StepicUser user) { - if (user == null) { - return new StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH); - } - return EduAdaptiveStepicConnector.checkChoiceTask(myTask, user); - } - - @Override - public void onTaskFailed(@NotNull String message) { - super.onTaskFailed(message); - repaintChoicePanel(project, myTask); - } - - private void repaintChoicePanel(@NotNull Project project, @NotNull ChoiceTask task) { - final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project); - if (toolWindow != null) { - toolWindow.setBottomComponent(new StudyChoiceVariantsPanel(task)); - } - } - }; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/CodeTask.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/CodeTask.java deleted file mode 100644 index e73f796763de..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/CodeTask.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat.tasks; - -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.actions.StudyCheckAction; -import com.jetbrains.edu.learning.checker.StudyCheckResult; -import com.jetbrains.edu.learning.checker.StudyCheckUtils; -import com.jetbrains.edu.learning.checker.StudyTaskChecker; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicUser; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class CodeTask extends Task { - @SuppressWarnings("unused") //used for deserialization - public CodeTask() {} - - public CodeTask(@NotNull final String name) { - super(name); - } - - @Override - public String getTaskType() { - return "code"; - } - - @Override - public StudyTaskChecker getChecker(@NotNull Project project) { - return new StudyTaskChecker(this, project) { - @Override - public void onTaskFailed(@NotNull String message) { - super.onTaskFailed("Wrong solution"); - StudyCheckUtils.showTestResultsToolWindow(myProject, message); - } - - @Override - public StudyCheckResult checkOnRemote(@Nullable StepicUser user) { - if (user == null) { - return new StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH); - } - return EduAdaptiveStepicConnector.checkCodeTask(myProject, myTask, user); - } - }; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/OutputTask.kt b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/OutputTask.kt deleted file mode 100644 index dce4402229fd..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/OutputTask.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat.tasks - -import com.intellij.openapi.project.Project -import com.jetbrains.edu.learning.checker.OutputTaskChecker - -/** - * Task type that allows to test output without any test files. - * Correct output is specified in output.txt file which is invisible for student. - * @see OutputTaskChecker - */ -class OutputTask : Task() { - companion object { - @JvmField - val OUTPUT_TASK_TYPE = "output" - } - - override fun getTaskType() = OUTPUT_TASK_TYPE - - override fun getChecker(project: Project) = OutputTaskChecker(this, project) -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/PyCharmTask.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/PyCharmTask.java deleted file mode 100644 index bf32ade4ba8a..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/PyCharmTask.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat.tasks; - -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.checker.StudyTaskChecker; -import com.jetbrains.edu.learning.courseFormat.Course; -import org.jetbrains.annotations.NotNull; - -/** - * Original PyCharm Edu tasks with local tests and answer placeholders - */ -public class PyCharmTask extends Task { - - public PyCharmTask() { - } - - public PyCharmTask(@NotNull String name) { - super(name); - } - - @Override - public String getTaskType() { - return "pycharm"; - } - - @Override - public StudyTaskChecker getChecker(@NotNull Project project) { - Course course = getLesson().getCourse(); - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - return configurator != null ? configurator.getPyCharmTaskChecker(this, project) : super.getChecker(project); - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java deleted file mode 100644 index 34f5e87f1bb9..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/Task.java +++ /dev/null @@ -1,326 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat.tasks; - -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import com.intellij.lang.Language; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.xmlb.XmlSerializer; -import com.intellij.util.xmlb.annotations.Transient; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.checker.StudyTaskChecker; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.*; -import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import one.util.streamex.EntryStream; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -/** - * Implementation of task which contains task files, tests, input file for tests - * - * To implement new task there are 4 steps to be done: - * - extend Task class - * - go to Lesson and update elementTypes in taskList AbstractCollection. Needed for proper xml serialization - * - Update TaskSerializer and TaskDeserializer in StudySerializationUtil to handle json serialization - * - for Adaptive tasks update taskTypes in EduAdaptiveStepicConnector so new task type can be added to a course - */ -public abstract class Task implements StudyItem { - @Expose private String name; - - // index is visible to user number of task from 1 to task number - private int myIndex; - protected StudyStatus myStatus = StudyStatus.Unchecked; - - @SerializedName("stepic_id") - @Expose private int myStepId; - - @SerializedName("task_files") - @Expose public Map taskFiles = new HashMap<>(); - - @SerializedName("test_files") - @Expose protected Map testsText = new HashMap<>(); - @SerializedName("task_texts") - @Expose protected Map taskTexts = new HashMap<>(); - - @Transient private Lesson myLesson; - @Expose @SerializedName("update_date") private Date myUpdateDate; - - public Task() {} - - public Task(@NotNull final String name) { - this.name = name; - } - - /** - * Initializes state of task file - * - * @param lesson lesson which task belongs to - */ - public void initTask(final Lesson lesson, boolean isRestarted) { - setLesson(lesson); - if (!isRestarted) myStatus = StudyStatus.Unchecked; - for (TaskFile taskFile : getTaskFiles().values()) { - taskFile.initTaskFile(this, isRestarted); - } - } - - @SuppressWarnings("unused") - //used for deserialization - public void setTaskTexts(Map taskTexts) { - this.taskTexts = taskTexts; - } - - @Override - public String getName() { - return name; - } - - @Override - public void setName(String name) { - this.name = name; - } - - @Override - public int getIndex() { - return myIndex; - } - - @Override - public void setIndex(int index) { - myIndex = index; - } - - public Map getTestsText() { - return testsText; - } - - @SuppressWarnings("unused") - //used for deserialization - public void setTestsText(Map testsText) { - this.testsText = testsText; - } - - public Map getTaskTexts() { - return taskTexts; - } - - public void addTestsTexts(String name, String text) { - testsText.put(name, text); - } - - public void addTaskText(String name, String text) { - taskTexts.put(name, text); - } - - public Map getTaskFiles() { - return taskFiles; - } - - @Nullable - public TaskFile getTaskFile(final String name) { - return name != null ? taskFiles.get(name) : null; - } - - public void addTaskFile(@NotNull final String name, int index) { - TaskFile taskFile = new TaskFile(); - taskFile.setIndex(index); - taskFile.setTask(this); - taskFile.name = name; - taskFiles.put(name, taskFile); - } - - public void addTaskFile(@NotNull final TaskFile taskFile) { - taskFiles.put(taskFile.name, taskFile); - } - - @Nullable - public TaskFile getFile(@NotNull final String fileName) { - return taskFiles.get(fileName); - } - - @Transient - public Lesson getLesson() { - return myLesson; - } - - @Transient - public void setLesson(Lesson lesson) { - myLesson = lesson; - } - - @Nullable - public VirtualFile getTaskDir(@NotNull final Project project) { - String lessonDirName = EduNames.LESSON + String.valueOf(myLesson.getIndex()); - String taskDirName = EduNames.TASK + String.valueOf(myIndex); - VirtualFile courseDir = project.getBaseDir(); - if (courseDir != null) { - VirtualFile lessonDir = courseDir.findChild(lessonDirName); - if (lessonDir != null) { - VirtualFile taskDir = lessonDir.findChild(taskDirName); - if (taskDir == null) { - return null; - } - VirtualFile srcDir = taskDir.findChild(EduNames.SRC); - return srcDir != null ? srcDir : taskDir; - } - } - return null; - } - - /** - * @param wrap if true, text will be wrapped with ancillary information (e.g. to display latex) - */ - public String getTaskDescription(boolean wrap) { - String fileName = getTaskDescriptionName(); - //TODO: replace this with simple get after implementing migration for taskTexts - Map.Entry entry = - EntryStream.of(taskTexts).findFirst(e -> FileUtil.getNameWithoutExtension(e.getKey()).equals(fileName)).orElse(null); - if (entry == null) { - return null; - } - String taskText = entry.getValue(); - if (!wrap) { - return taskText; - } - taskText = StudyUtils.wrapTextToDisplayLatex(StudyUtils.convertToHtml(taskText)); - if (getLesson().getCourse().isAdaptive()) { - taskText = EduAdaptiveStepicConnector.wrapAdaptiveCourseText(this, taskText); - } - return taskText; - } - - public String getTaskDescription() { - return getTaskDescription(true); - } - - public String getTaskDescriptionName() { - return EduNames.TASK; - } - - @NotNull - public String getTestsText(@NotNull final Project project) { - final Course course = getLesson().getCourse(); - final Language language = course.getLanguageById(); - final EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); - final VirtualFile taskDir = getTaskDir(project); - if (taskDir != null) { - final VirtualFile file = taskDir.findChild(configurator.getTestFileName()); - if (file == null) return ""; - final Document document = FileDocumentManager.getInstance().getDocument(file); - if (document != null) { - return document.getImmutableCharSequence().toString(); - } - } - - return ""; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Task task = (Task)o; - - if (myIndex != task.myIndex) return false; - if (name != null ? !name.equals(task.name) : task.name != null) return false; - if (taskFiles != null ? !taskFiles.equals(task.taskFiles) : task.taskFiles != null) return false; - if (taskTexts != null ? !taskTexts.equals(task.taskTexts) : task.taskTexts != null) return false; - if (testsText != null ? !testsText.equals(task.testsText) : task.testsText != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + myIndex; - result = 31 * result + (taskFiles != null ? taskFiles.hashCode() : 0); - result = 31 * result + (taskTexts != null ? taskTexts.hashCode() : 0); - result = 31 * result + (testsText != null ? testsText.hashCode() : 0); - return result; - } - - public void setStepId(int stepId) { - myStepId = stepId; - } - - public int getStepId() { - return myStepId; - } - - public StudyStatus getStatus() { - return myStatus; - } - - public void setStatus(StudyStatus status) { - for (TaskFile taskFile : taskFiles.values()) { - for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) { - placeholder.setStatus(status); - } - } - myStatus = status; - } - - public Task copy() { - Element element = XmlSerializer.serialize(this); - Task copy = XmlSerializer.deserialize(element, getClass()); - copy.initTask(null, true); - return copy; - } - - public void setUpdateDate(Date date) { - myUpdateDate = date; - } - - public Date getUpdateDate() { - return myUpdateDate; - } - - public boolean isUpToDate() { - if (getStepId() == 0) return true; - final Date date = EduStepicConnector.getTaskUpdateDate(getStepId()); - if (date == null) return true; - if (myUpdateDate == null) return false; - return !date.after(myUpdateDate); - } - - public void copyTaskParameters(Task task) { - setName(task.getName()); - setIndex(task.getIndex()); - setStatus(task.getStatus()); - setStepId(task.getStepId()); - taskFiles = task.getTaskFiles(); - testsText = task.getTestsText(); - taskTexts = task.getTaskTexts(); - setLesson(task.getLesson()); - setUpdateDate(task.getUpdateDate()); - } - - // used in json serialization/deserialization - public abstract String getTaskType(); - - public StudyTaskChecker getChecker(@NotNull Project project) { - return new StudyTaskChecker<>(this, project); - } - - public int getPosition() { - final Lesson lesson = getLesson(); - return lesson.getTaskList().indexOf(this) + 1; - } - - public void saveTaskText(String text) { - taskTexts.put(getTaskDescriptionName(), text); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TaskWithSubtasks.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TaskWithSubtasks.java deleted file mode 100644 index a3891a0a1e90..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TaskWithSubtasks.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat.tasks; - -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.checker.StudyTaskChecker; -import com.jetbrains.edu.learning.checker.TaskWithSubtasksChecker; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; - -public class TaskWithSubtasks extends PyCharmTask { - private int myActiveSubtaskIndex = 0; - @SerializedName("last_subtask_index") - @Expose private int myLastSubtaskIndex = 0; - - public TaskWithSubtasks() {} - - public TaskWithSubtasks(@NotNull final String name) { - super(name); - } - - @Override - public String getTaskDescriptionName() { - return super.getTaskDescriptionName() + EduNames.SUBTASK_MARKER + myActiveSubtaskIndex; - } - - public TaskWithSubtasks(Task task) { - copyTaskParameters(task); - } - - public int getActiveSubtaskIndex() { - return myActiveSubtaskIndex; - } - - public void setActiveSubtaskIndex(int activeSubtaskIndex) { - myActiveSubtaskIndex = activeSubtaskIndex; - } - - public int getLastSubtaskIndex() { - return myLastSubtaskIndex; - } - - public void setLastSubtaskIndex(int lastSubtaskIndex) { - myLastSubtaskIndex = lastSubtaskIndex; - } - - public void setStatus(StudyStatus status) { - for (TaskFile taskFile : taskFiles.values()) { - for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) { - placeholder.setStatus(status); - } - } - if (status == StudyStatus.Solved) { - if (activeSubtaskNotLast()) { - if (myStatus == StudyStatus.Failed) { - myStatus = StudyStatus.Unchecked; - } - } - else { - myStatus = StudyStatus.Solved; - } - } - } - - public boolean activeSubtaskNotLast() { - return getActiveSubtaskIndex() != getLastSubtaskIndex(); - } - - public String getTaskType() { - return "subtasks"; - } - - @Override - public StudyTaskChecker getChecker(@NotNull Project project) { - return new TaskWithSubtasksChecker(this, project); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java deleted file mode 100644 index 2e39f96f4059..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.jetbrains.edu.learning.courseFormat.tasks; - -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.checker.StudyCheckResult; -import com.jetbrains.edu.learning.checker.StudyTaskChecker; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.stepic.StepicUser; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class TheoryTask extends Task { - @SuppressWarnings("unused") //used for deserialization - public TheoryTask() {} - - public TheoryTask(@NotNull final String name) { - super(name); - } - - @Override - public String getTaskType() { - return "theory"; - } - - @Override - public StudyTaskChecker getChecker(@NotNull Project project) { - return new StudyTaskChecker(this, project) { - @Override - public void onTaskSolved(@NotNull String message) { - } - - @Override - public StudyCheckResult check() { - return new StudyCheckResult(StudyStatus.Solved, ""); - } - - @Override - public StudyCheckResult checkOnRemote(@Nullable StepicUser user) { - return check(); - } - }; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java deleted file mode 100644 index 3293c345b8f2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.jetbrains.edu.learning.courseGeneration; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.vfs.VfsUtil; -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.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.apache.commons.codec.binary.Base64; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -public class StudyGenerator { - private static final Logger LOG = Logger.getInstance(StudyGenerator.class.getName()); - - private StudyGenerator() {} - - public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir) { - try { - final List lessons = course.getLessons(true); - for (int i = 1; i <= lessons.size(); i++) { - Lesson lesson = lessons.get(i - 1); - lesson.setIndex(i); - createLesson(lesson, baseDir); - } - } - catch (IOException e) { - LOG.error(e); - } - } - - public static void createLesson(@NotNull final Lesson lesson, @NotNull final VirtualFile courseDir) throws IOException { - if (EduNames.PYCHARM_ADDITIONAL.equals(lesson.getName())) { - createAdditionalFiles(lesson, courseDir); - } - else { - String lessonDirName = EduNames.LESSON + Integer.toString(lesson.getIndex()); - VirtualFile lessonDir = VfsUtil.createDirectoryIfMissing(courseDir, lessonDirName); - final List taskList = lesson.getTaskList(); - for (int i = 1; i <= taskList.size(); i++) { - Task task = taskList.get(i - 1); - task.setIndex(i); - createTask(task, lessonDir); - } - } - } - - public static void createTask(@NotNull final Task task, @NotNull final VirtualFile lessonDir) throws IOException { - String name = EduNames.TASK + Integer.toString(task.getIndex()); - VirtualFile taskDir = VfsUtil.createDirectoryIfMissing(lessonDir, name); - createTaskContent(task, taskDir); - } - - public static void createTaskContent(@NotNull Task task, @NotNull VirtualFile taskDir) throws IOException { - int i = 0; - for (Map.Entry taskFile : task.getTaskFiles().entrySet()) { - TaskFile taskFileContent = taskFile.getValue(); - taskFileContent.setIndex(i); - i++; - createTaskFile(taskDir, taskFile.getValue()); - } - createTestFiles(taskDir, task); - } - - public static void createTaskFile(@NotNull final VirtualFile taskDir, @NotNull final TaskFile taskFile) throws IOException { - final String name = taskFile.name; - createChildFile(taskDir, name, taskFile.text); - } - - private static void createTestFiles(@NotNull VirtualFile taskDir, @NotNull Task task) throws IOException { - final Map tests = task.getTestsText(); - createFiles(taskDir, tests); - } - - private static void createFiles(@NotNull VirtualFile taskDir, @NotNull Map texts) throws IOException { - for (Map.Entry entry : texts.entrySet()) { - final String name = entry.getKey(); - VirtualFile virtualTaskFile = taskDir.findChild(name); - if (virtualTaskFile == null) { - createChildFile(taskDir, name, entry.getValue()); - } - } - } - - private static void createAdditionalFiles(@NotNull Lesson lesson, @NotNull VirtualFile courseDir) throws IOException { - final List taskList = lesson.getTaskList(); - if (taskList.size() != 1) return; - final Task task = taskList.get(0); - for (Map.Entry entry : task.getTestsText().entrySet()) { - createChildFile(courseDir, entry.getKey(), entry.getValue()); - } - } - - public static void createChildFile(@NotNull VirtualFile taskDir, @NotNull String name, @NotNull String text) throws IOException { - String newDirectories = null; - String fileName = name; - VirtualFile dir = taskDir; - if (name.contains("/")) { - int pos = name.lastIndexOf("/"); - fileName = name.substring(pos + 1); - newDirectories = name.substring(0, pos); - } - if (newDirectories != null) { - dir = VfsUtil.createDirectoryIfMissing(taskDir, newDirectories); - } - if (dir != null) { - VirtualFile virtualTaskFile = dir.findChild(fileName); - if (virtualTaskFile == null) { - virtualTaskFile = dir.createChildData(taskDir, fileName); - } - if (EduUtils.isImage(name)) { - virtualTaskFile.setBinaryContent(Base64.decodeBase64(text)); - } - else { - VfsUtil.saveText(virtualTaskFile, text); - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java deleted file mode 100644 index acd771264c03..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.jetbrains.edu.learning.courseGeneration; - -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.intellij.facet.ui.ValidationResult; -import com.intellij.lang.LanguageExtensionPoint; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.io.zip.JBZipEntry; -import com.intellij.util.io.zip.JBZipFile; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudySerializationUtils; -import com.jetbrains.edu.learning.StudySettings; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicUser; -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 static com.jetbrains.edu.learning.StudyUtils.execCancelable; - -public class StudyProjectGenerator { - private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName()); - private final List myListeners = ContainerUtil.newArrayList(); - private List myCourses = new ArrayList<>(); - private List myEnrolledCoursesIds = new ArrayList<>(); - protected Course mySelectedCourse; - - public void setCourses(List courses) { - myCourses = courses; - } - - public boolean isLoggedIn() { - final StepicUser user = StudySettings.getInstance().getUser(); - return user != null; - } - - public void setEnrolledCoursesIds(@NotNull final List coursesIds) { - myEnrolledCoursesIds = coursesIds; - } - - @NotNull - public List getEnrolledCoursesIds() { - return myEnrolledCoursesIds; - } - - public void setSelectedCourse(@NotNull final Course course) { - mySelectedCourse = course; - } - - public Course getSelectedCourse() { - return mySelectedCourse; - } - - public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir) { - final Course course = getCourse(project); - if (course == null) { - LOG.warn("Course is null"); - Messages.showWarningDialog("Some problems occurred while creating the course", "Error in Course Creation"); - return; - } - else if (course.isAdaptive() && !StudyUtils.isCourseValid(course)) { - Messages.showWarningDialog("There is no recommended tasks for this adaptive course", "Error in Course Creation"); - return; - } - StudyTaskManager.getInstance(project).setCourse(course); - ApplicationManager.getApplication().runWriteAction(() -> { - StudyGenerator.createCourse(course, baseDir); - StudyUtils.registerStudyToolWindow(course, project); - StudyUtils.openFirstTask(course, project); - EduUsagesCollector.projectTypeCreated(course.isAdaptive() ? EduNames.ADAPTIVE : EduNames.STUDY); - }); - } - - @Nullable - public Course getCourse(@NotNull final Project project) { - if (mySelectedCourse instanceof RemoteCourse) { - return getCourseFromStepic(project, (RemoteCourse)mySelectedCourse); - } - mySelectedCourse.initCourse(false); - return mySelectedCourse; - } - - private static RemoteCourse getCourseFromStepic(@NotNull Project project, RemoteCourse selectedCourse) { - return ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { - ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - return execCancelable(() -> { - final RemoteCourse course = EduStepicConnector.getCourse(project, selectedCourse); - if (StudyUtils.isCourseValid(course)) { - course.initCourse(false); - } - return course; - }); - }, "Creating Course", true, project); - } - - // Supposed to be called under progress - public List getCourses(boolean force) { - if (force) { - myCourses = execCancelable(() -> EduStepicConnector.getCourses(StudySettings.getInstance().getUser())); - } - if (myCourses == null || myCourses.isEmpty() || (myCourses.size() == 1 && myCourses.contains(Course.INVALID_COURSE))) { - myCourses = getBundledCourses(); - } - sortCourses(myCourses); - return myCourses; - } - - public void sortCourses(List result) { - // sort courses so as to have non-adaptive courses in the beginning of the list - Collections.sort(result, (c1, c2) -> { - if (mySelectedCourse != null) { - if (mySelectedCourse.equals(c1)) { - return -1; - } - if (mySelectedCourse.equals(c2)) { - return 1; - } - } - if ((c1.isAdaptive() && c2.isAdaptive()) || (!c1.isAdaptive() && !c2.isAdaptive())) { - return 0; - } - return c1.isAdaptive() ? 1 : -1; - }); - } - - @NotNull - public List getCoursesUnderProgress(boolean force, @NotNull final String progressTitle, @Nullable final Project project) { - try { - return ProgressManager.getInstance() - .runProcessWithProgressSynchronously(() -> { - ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - return getCourses(force); - }, progressTitle, true, project); - } - catch (RuntimeException e) { - return Collections.singletonList(Course.INVALID_COURSE); - } - } - - public void addSettingsStateListener(@NotNull SettingsListener listener) { - myListeners.add(listener); - } - - public interface SettingsListener { - void stateChanged(ValidationResult result); - } - - public void fireStateChanged(ValidationResult result) { - for (SettingsListener listener : myListeners) { - listener.stateChanged(result); - } - } - - @Nullable - public List getBundledCourses() { - final ArrayList courses = new ArrayList<>(); - final LanguageExtensionPoint[] extensions = Extensions.getExtensions(EduPluginConfigurator.EP_NAME, null); - for (LanguageExtensionPoint extension : extensions) { - final EduPluginConfigurator configurator = extension.getInstance(); - final List paths = configurator.getBundledCoursePaths(); - for (String path : paths) { - courses.add(getCourse(path)); - } - } - return courses; - } - - @Nullable - public Course addLocalCourse(String zipFilePath) { - final Course courseInfo = getCourse(zipFilePath); - if (courseInfo != null) { - myCourses.add(0, courseInfo); - } - return courseInfo; - } - - @Nullable - public static Course getCourse(String zipFilePath) { - try { - final JBZipFile zipFile = new JBZipFile(zipFilePath); - final JBZipEntry entry = zipFile.getEntry(EduNames.COURSE_META_FILE); - if (entry == null) { - return null; - } - byte[] bytes = entry.getData(); - final String jsonText = new String(bytes, CharsetToolkit.UTF8_CHARSET); - Gson gson = new GsonBuilder() - .registerTypeAdapter(Task.class, new StudySerializationUtils.Json.TaskAdapter()) - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .create(); - return gson.fromJson(jsonText, Course.class); - } - catch (IOException e) { - LOG.error("Failed to unzip course archive"); - LOG.error(e); - } - return null; - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyChoiceVariantsPanel.kt b/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyChoiceVariantsPanel.kt deleted file mode 100644 index 6fefd3e69956..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyChoiceVariantsPanel.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.jetbrains.edu.learning.editor - -import com.intellij.ide.ui.LafManager -import com.intellij.ide.ui.LafManagerListener -import com.intellij.ide.ui.laf.darcula.DarculaLookAndFeelInfo -import com.intellij.openapi.editor.colors.EditorColorsManager -import com.intellij.util.ui.UIUtil -import com.jetbrains.edu.learning.courseFormat.tasks.ChoiceTask -import javafx.application.Platform -import javafx.beans.value.ObservableValue -import javafx.embed.swing.JFXPanel -import javafx.geometry.Insets -import javafx.scene.Group -import javafx.scene.Scene -import javafx.scene.control.ButtonBase -import javafx.scene.control.CheckBox -import javafx.scene.control.RadioButton -import javafx.scene.control.ToggleGroup -import javafx.scene.layout.VBox -import javafx.scene.paint.Color -import javafx.scene.text.Font -import java.util.* -import javax.swing.JScrollPane -import javax.swing.ScrollPaneConstants - - -class StudyChoiceVariantsPanel(task: ChoiceTask) : JScrollPane() { - - private val LEFT_INSET = 15.0 - private val RIGHT_INSET = 10.0 - private val TOP_INSET = 15.0 - private val BOTTOM_INSET = 10.0 - - private val buttons: ArrayList = ArrayList() - - init { - val jfxPanel = JFXPanel() - LafManager.getInstance().addLafManagerListener(StudyLafManagerListener(jfxPanel)) - Platform.runLater { - val group = Group() - val scene = Scene(group, getSceneBackground()) - jfxPanel.scene = scene - val vBox = VBox() - vBox.spacing = 10.0 - vBox.padding = Insets(TOP_INSET, RIGHT_INSET, BOTTOM_INSET, LEFT_INSET) - if (task.isMultipleChoice) { - for ((index, variant) in task.choiceVariants.withIndex()) { - val isSelected = task.selectedVariants.contains(index) - val checkBox = CheckBox(variant) - checkBox.isSelected = isSelected - checkBox.selectedProperty().addListener(createSelectionListener(task, index)) - setUpButtonStyle(checkBox, scene) - vBox.children.add(checkBox) - buttons.add(checkBox) - } - } - else { - val toggleGroup = ToggleGroup() - for ((index, variant) in task.choiceVariants.withIndex()) { - val isSelected = task.selectedVariants.contains(index) - val radioButton = RadioButton(variant) - radioButton.toggleGroup = toggleGroup - radioButton.isSelected = isSelected - radioButton.selectedProperty().addListener(createSelectionListener(task, index)) - setUpButtonStyle(radioButton, scene) - vBox.children.add(radioButton) - buttons.add(radioButton) - } - } - - group.children.add(vBox) - } - setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER) - setViewportView(jfxPanel) - } - - private fun getSceneBackground(): Color { - val isDarcula = LafManager.getInstance().currentLookAndFeel is DarculaLookAndFeelInfo - val panelBackground = if (isDarcula) UIUtil.getPanelBackground() else UIUtil.getTextFieldBackground() - return Color.rgb(panelBackground.red, panelBackground.green, panelBackground.blue) - } - - private fun createSelectionListener(task: ChoiceTask, index: Int): (ObservableValue, Boolean, Boolean) -> Unit { - return { observableValue, wasSelected, isSelected -> - if (isSelected) { - task.selectedVariants.add(index) - } - else { - task.selectedVariants.remove(index) - } - } - } - - private fun setUpButtonStyle(button: ButtonBase, scene: Scene) { - button.isWrapText = true - button.maxWidthProperty().bind(scene.widthProperty().subtract(LEFT_INSET).subtract(RIGHT_INSET)) - button.font = Font.font((EditorColorsManager.getInstance().globalScheme.editorFontSize + 2).toDouble()) - - setButtonLaf(button) - } - - private fun setButtonLaf(button: ButtonBase) { - val darcula = LafManager.getInstance().currentLookAndFeel is DarculaLookAndFeelInfo - val stylesheetPath = if (darcula) "/style/buttonsDarcula.css" else "/style/buttons.css" - button.stylesheets.removeAll() - button.stylesheets.add(javaClass.getResource(stylesheetPath).toExternalForm()) - } - - private inner class StudyLafManagerListener(val jfxPanel: JFXPanel) : LafManagerListener { - override fun lookAndFeelChanged(manager: LafManager) { - Platform.runLater { - val panelBackground = UIUtil.getPanelBackground() - jfxPanel.scene.fill = Color.rgb(panelBackground.red, panelBackground.green, panelBackground.blue) - for (button in buttons) { - setButtonLaf(button) - } - jfxPanel.repaint() - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyEditor.java b/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyEditor.java deleted file mode 100644 index 631a3f762892..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyEditor.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jetbrains.edu.learning.editor; - -import com.intellij.openapi.editor.Document; -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 org.jetbrains.annotations.NotNull; - -import java.util.HashMap; -import java.util.Map; - -/** - * Implementation of StudyEditor which has panel with special buttons and task text - * also @see {@link StudyFileEditorProvider} - */ -public class StudyEditor extends PsiAwareTextEditorImpl { - private final TaskFile myTaskFile; - private static final Map myDocumentListeners = new HashMap<>(); - - 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; - } - - public static void addDocumentListener(@NotNull final Document document, @NotNull final EduDocumentListener listener) { - document.addDocumentListener(listener); - myDocumentListeners.put(document, listener); - } - - public static void removeListener(Document document) { - final EduDocumentListener listener = myDocumentListeners.get(document); - if (listener != null) { - document.removeDocumentListener(listener); - } - myDocumentListeners.remove(document); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java b/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java deleted file mode 100644 index a3f625f045be..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyEditorFactoryListener.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.jetbrains.edu.learning.editor; - - -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.LogicalPosition; -import com.intellij.openapi.editor.event.EditorFactoryEvent; -import com.intellij.openapi.editor.event.EditorFactoryListener; -import com.intellij.openapi.editor.event.EditorMouseAdapter; -import com.intellij.openapi.editor.event.EditorMouseEvent; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.problems.WolfTheProblemSolver; -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.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import com.jetbrains.edu.learning.ui.StudyToolWindowFactory; -import org.jetbrains.annotations.NotNull; - -import java.awt.*; - - -public class StudyEditorFactoryListener implements EditorFactoryListener { - - private static class WindowSelectionListener extends EditorMouseAdapter { - private final TaskFile myTaskFile; - - WindowSelectionListener(@NotNull final TaskFile taskFile) { - myTaskFile = taskFile; - } - - @Override - public void mouseClicked(EditorMouseEvent e) { - final Editor editor = e.getEditor(); - final Point point = e.getMouseEvent().getPoint(); - final LogicalPosition pos = editor.xyToLogicalPosition(point); - final AnswerPlaceholder answerPlaceholder = myTaskFile.getAnswerPlaceholder(editor.logicalPositionToOffset(pos)); - if (answerPlaceholder == null || answerPlaceholder.getSelected()) { - return; - } - final Pair offsets = StudyUtils.getPlaceholderOffsets(answerPlaceholder, editor.getDocument()); - editor.getSelectionModel().setSelection(offsets.getFirst(), offsets.getSecond()); - answerPlaceholder.setSelected(true); - } - } - - @Override - public void editorCreated(@NotNull final EditorFactoryEvent event) { - final Editor editor = event.getEditor(); - final Project project = editor.getProject(); - if (project == null) { - return; - } - - final Document document = editor.getDocument(); - final VirtualFile openedFile = FileDocumentManager.getInstance().getFile(document); - if (openedFile != null) { - final TaskFile taskFile = StudyUtils.getTaskFile(project, openedFile); - if (taskFile != null) { - WolfTheProblemSolver.getInstance(project).clearProblems(openedFile); - final ToolWindow studyToolWindow = ToolWindowManager.getInstance(project).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW); - if (studyToolWindow != null) { - StudyUtils.updateToolWindows(project); - studyToolWindow.show(null); - } - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - - StudyEditor.addDocumentListener(document, new EduDocumentListener(taskFile, true)); - - if (!taskFile.getAnswerPlaceholders().isEmpty()) { - StudyNavigator.navigateToFirstAnswerPlaceholder(editor, taskFile); - boolean isStudyProject = EduNames.STUDY.equals(course.getCourseMode()); - StudyUtils.drawAllAnswerPlaceholders(editor, taskFile); - if (isStudyProject) { - editor.addEditorMouseListener(new WindowSelectionListener(taskFile)); - } - } - } - } - } - - @Override - public void editorReleased(@NotNull EditorFactoryEvent event) { - final Editor editor = event.getEditor(); - final Document document = editor.getDocument(); - StudyEditor.removeListener(document); - editor.getMarkupModel().removeAllHighlighters(); - editor.getSelectionModel().removeSelection(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java b/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java deleted file mode 100644 index 34ee809bb0cf..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.jetbrains.edu.learning.editor; - -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorPolicy; -import com.intellij.openapi.fileEditor.FileEditorProvider; -import com.intellij.openapi.fileEditor.FileEditorState; -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.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; - -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) { - TaskFile taskFile = StudyUtils.getTaskFile(project, file); - return taskFile != null && !taskFile.isUserCreated() && TextEditorProvider.isTextFile(file); - } - - @NotNull - @Override - public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) { - return new StudyEditor(project, file); - } - - @Override - public void disposeEditor(@NotNull FileEditor editor) { - defaultTextEditorProvider.disposeEditor(editor); - } - - @NotNull - @Override - public FileEditorState readState(@NotNull Element sourceElement, @NotNull Project project, @NotNull VirtualFile file) { - return defaultTextEditorProvider.readState(sourceElement, project, file); - } - - @Override - public void writeState(@NotNull FileEditorState state, @NotNull Project project, @NotNull Element targetElement) { - defaultTextEditorProvider.writeState(state, project, targetElement); - } - - @NotNull - @Override - public String getEditorTypeId() { - return EDITOR_TYPE_ID; - } - - @NotNull - @Override - public FileEditorPolicy getPolicy() { - return FileEditorPolicy.HIDE_DEFAULT_EDITOR; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyHighlightErrorFilter.java b/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyHighlightErrorFilter.java deleted file mode 100644 index 38bae134e235..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/editor/StudyHighlightErrorFilter.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.jetbrains.edu.learning.editor; - -import com.intellij.codeInsight.highlighting.HighlightErrorFilter; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiErrorElement; -import com.intellij.psi.PsiFile; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.StudyUtils; -import org.jetbrains.annotations.NotNull; - -public class StudyHighlightErrorFilter extends HighlightErrorFilter{ - @Override - public boolean shouldHighlightErrorElement(@NotNull PsiErrorElement element) { - PsiFile file = element.getContainingFile(); - if (file == null) { - return true; - } - VirtualFile virtualFile = file.getVirtualFile(); - if (virtualFile == null) { - return true; - } - TaskFile taskFile = StudyUtils.getTaskFile(element.getProject(), virtualFile); - return taskFile == null || taskFile.isHighlightErrors(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntelliJProjectTemplate.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntelliJProjectTemplate.java deleted file mode 100644 index bff27ad415c1..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntelliJProjectTemplate.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.jetbrains.edu.learning.intellij; - -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.platform.ProjectTemplate; - -public interface EduIntelliJProjectTemplate extends ProjectTemplate { - ExtensionPointName EP_NAME = - ExtensionPointName.create("Edu.eduIntelliJProjectTemplate"); -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntelliJTemplateFactory.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntelliJTemplateFactory.java deleted file mode 100644 index 73be3a89bcc2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntelliJTemplateFactory.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jetbrains.edu.learning.intellij; - -import com.intellij.icons.AllIcons; -import com.intellij.ide.util.projectWizard.WizardContext; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.platform.ProjectTemplate; -import com.intellij.platform.ProjectTemplatesFactory; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.intellij.stepik.EduRemoteCourseTemplate; -import com.jetbrains.edu.learning.stepic.EduCourseUpdater; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@SuppressWarnings("unused") //used in other educational plugins that are stored in separate repository -public class EduIntelliJTemplateFactory extends ProjectTemplatesFactory { - private static final String GROUP_NAME = "Education"; - - @NotNull - @Override - public String[] getGroups() { - return new String[]{GROUP_NAME}; - } - - @NotNull - @Override - public ProjectTemplate[] createTemplates(@Nullable String group, WizardContext context) { - final ArrayList templates = new ArrayList<>(); - final List courses = EduCourseUpdater.getInstance().getRemoteCourses(); - for (Course course : courses) { - templates.add(new EduRemoteCourseTemplate(course)); - } - Collections.addAll(templates, ApplicationManager.getApplication().getExtensions(EduIntelliJProjectTemplate.EP_NAME)); - return templates.toArray(new ProjectTemplate[templates.size()]); - } - - @Override - public Icon getGroupIcon(String group) { - return AllIcons.Modules.Types.UserDefined; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntellijApplicationComponent.kt b/python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntellijApplicationComponent.kt deleted file mode 100644 index af0f9461f860..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/EduIntellijApplicationComponent.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.jetbrains.edu.learning.intellij - -import com.intellij.ide.plugins.PluginManager -import com.intellij.ide.plugins.PluginManagerCore -import com.intellij.openapi.components.ApplicationComponent -import com.intellij.openapi.extensions.PluginId - -class EduIntellijApplicationComponent : ApplicationComponent { - override fun initComponent() { - val id = "com.jetbrains.edu.intellij" - val plugin = PluginManager.getPlugin(PluginId.getId(id)) ?: return - if (plugin.isEnabled) { - PluginManagerCore.disablePlugin(id) - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/android/EduSelectCourseProjectWizard.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/android/EduSelectCourseProjectWizard.java deleted file mode 100644 index bf580ac678e1..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/android/EduSelectCourseProjectWizard.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.jetbrains.edu.learning.intellij.android; - -import com.intellij.ide.projectWizard.ProjectSettingsStep; -import com.intellij.ide.projectWizard.ProjectTemplateList; -import com.intellij.ide.util.newProjectWizard.AbstractProjectWizard; -import com.intellij.ide.util.newProjectWizard.StepSequence; -import com.intellij.ide.util.projectWizard.ModuleWizardStep; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.roots.ui.configuration.ModulesProvider; -import com.intellij.platform.ProjectTemplate; -import com.jetbrains.edu.learning.intellij.EduIntelliJProjectTemplate; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -class EduSelectCourseProjectWizard extends AbstractProjectWizard { - - private StepSequence mySequence = new StepSequence(); - - EduSelectCourseProjectWizard() { - super("New Course", null, (String)null); - init(); - } - - protected void init() { - myWizardContext.setModulesProvider(ModulesProvider.EMPTY_MODULES_PROVIDER); - MyChooseCourseStep step = new MyChooseCourseStep(); - mySequence.addCommonStep(step); - mySequence.addCommonFinishingStep(new ProjectSettingsStep(myWizardContext), null); - mySequence.getAllSteps().forEach(this::addStep); - super.init(); - } - - @Override - public StepSequence getSequence() { - return mySequence; - } - - private class MyChooseCourseStep extends ModuleWizardStep { - private ProjectTemplateList myTemplateList = new ProjectTemplateList(); - - @Override - public JComponent getComponent() { - return myTemplateList; - } - - @Override - public void updateDataModel() { - myWizardContext.setProjectTemplate(myTemplateList.getSelectedTemplate()); - } - - @Override - public void updateStep() { - List templates = new ArrayList<>(); - Collections.addAll(templates, ApplicationManager.getApplication().getExtensions(EduIntelliJProjectTemplate.EP_NAME)); - myTemplateList.setTemplates(templates, false); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/android/EduStartLearningAction.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/android/EduStartLearningAction.java deleted file mode 100644 index 01f253a268a8..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/android/EduStartLearningAction.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.jetbrains.edu.learning.intellij.android; - -import com.intellij.icons.AllIcons; -import com.intellij.ide.impl.NewProjectUtil; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.util.PlatformUtils; -import com.jetbrains.edu.learning.intellij.EduIntelliJProjectTemplate; - -public class EduStartLearningAction extends AnAction { - - public EduStartLearningAction() { - super(AllIcons.Modules.Types.UserDefined); - } - - @Override - public void actionPerformed(AnActionEvent e) { - EduSelectCourseProjectWizard wizard = new EduSelectCourseProjectWizard(); - NewProjectUtil.createNewProject(AnAction.getEventProject(e), wizard); - } - - @Override - public void update(AnActionEvent e) { - if (ApplicationManager.getApplication().getExtensions(EduIntelliJProjectTemplate.EP_NAME).length < 1) { - e.getPresentation().setEnabledAndVisible(false); - } - if (!PlatformUtils.isJetBrainsProduct()) { - e.getPresentation().setEnabledAndVisible(false); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduCourseModuleBuilder.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduCourseModuleBuilder.java deleted file mode 100644 index bf4ee029deb6..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduCourseModuleBuilder.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.jetbrains.edu.learning.intellij.generation; - -import com.intellij.ide.util.projectWizard.JavaModuleBuilder; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.roots.ModifiableRootModel; - -import java.util.Collections; - -public class EduCourseModuleBuilder extends JavaModuleBuilder { - - @Override - public void setupRootModel(ModifiableRootModel rootModel) throws ConfigurationException { - setSourcePaths(Collections.emptyList()); - super.setupRootModel(rootModel); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduProjectGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduProjectGenerator.java deleted file mode 100644 index 6cac20d04ace..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/generation/EduProjectGenerator.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.jetbrains.edu.learning.intellij.generation; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; -import org.jetbrains.annotations.NotNull; - -public class EduProjectGenerator extends StudyProjectGenerator { - private static final Logger LOG = Logger.getInstance(EduProjectGenerator.class); - - @Override - public void generateProject(@NotNull Project project, @NotNull VirtualFile baseDir) { - final Course course = getCourse(project); - if (course == null) { - LOG.warn("Failed to get course"); - return; - } - StudyTaskManager.getInstance(project).setCourse(course); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduCustomCourseModuleBuilder.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduCustomCourseModuleBuilder.java deleted file mode 100644 index c6020344e10e..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduCustomCourseModuleBuilder.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.jetbrains.edu.learning.intellij.localCourses; - -import com.intellij.ide.util.projectWizard.*; -import com.intellij.lang.Language; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.ModifiableModuleModel; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleWithNameAlreadyExists; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ui.configuration.ModulesProvider; -import com.intellij.openapi.util.Conditions; -import com.intellij.openapi.util.InvalidDataException; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; -import com.jetbrains.edu.learning.intellij.generation.EduProjectGenerator; -import org.jdom.JDOMException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.io.IOException; - -class EduCustomCourseModuleBuilder extends JavaModuleBuilder { - private static final Logger LOG = Logger.getInstance(EduCustomCourseModuleBuilder.class); - private EduProjectGenerator myGenerator = new EduProjectGenerator(); - private Course mySelectedCourse; - - @Nullable - @Override - public Module commitModule(@NotNull Project project, @Nullable ModifiableModuleModel model) { - Module module = super.commitModule(project, model); - if (module == null) { - return null; - } - String languageName = mySelectedCourse.getLanguageID(); - Language language = Language.findLanguageByID(languageName); - if (language == null) { - return module; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); - if (configurator == null) { - return module; - } - configurator.configureModule(module); - return module; - } - - - @Nullable - @Override - public String getBuilderId() { - return "custom.course.builder"; - } - - @Override - public ModuleWizardStep[] createWizardSteps(@NotNull WizardContext wizardContext, @NotNull ModulesProvider modulesProvider) { - return new ModuleWizardStep[]{new EduCourseSelectionSettingsStep(myGenerator)}; - } - - - private class EduCourseSelectionSettingsStep extends ModuleWizardStep { - - private final StudyProjectGenerator myGenerator; - - EduCourseSelectionSettingsStep(StudyProjectGenerator generator) { - myGenerator = generator; - } - - @Override - public JComponent getComponent() { - return new EduLocalCoursePanel(myGenerator, EduCustomCourseModuleBuilder.this).getContentPanel(); - } - - @Override - public void updateDataModel() { - } - } - - @Nullable - @Override - public ModuleWizardStep modifySettingsStep(@NotNull SettingsStep settingsStep) { - return ProjectWizardStepFactory.getInstance().createJavaSettingsStep(settingsStep, this, Conditions.alwaysTrue()); - } - - void setSelectedCourse(Course selectedCourse) { - mySelectedCourse = selectedCourse; - } - - @NotNull - @Override - public Module createModule(@NotNull ModifiableModuleModel moduleModel) - throws InvalidDataException, IOException, ModuleWithNameAlreadyExists, JDOMException, ConfigurationException { - Module baseModule = super.createModule(moduleModel); - if (mySelectedCourse != null) { - String languageName = mySelectedCourse.getLanguageID(); - Language language = Language.findLanguageByID(languageName); - if (language != null) { - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); - if (configurator != null) { - myGenerator.setSelectedCourse(mySelectedCourse); - Project project = baseModule.getProject(); - myGenerator.generateProject(project, project.getBaseDir()); - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - LOG.info("failed to generate course"); - return baseModule; - } - configurator.createCourseModuleContent(moduleModel, project, course, getModuleFileDirectory()); - } - } - } - return baseModule; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduCustomCourseProjectTemplate.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduCustomCourseProjectTemplate.java deleted file mode 100644 index 378200a8ef58..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduCustomCourseProjectTemplate.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jetbrains.edu.learning.intellij.localCourses; - -import com.intellij.icons.AllIcons; -import com.intellij.ide.util.projectWizard.AbstractModuleBuilder; -import com.intellij.openapi.ui.ValidationInfo; -import com.jetbrains.edu.learning.intellij.EduIntelliJProjectTemplate; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -class EduCustomCourseProjectTemplate implements EduIntelliJProjectTemplate { - @NotNull - @Override - public String getName() { - return "Load an existing course"; - } - - @Nullable - @Override - public String getDescription() { - return "Select and add local course from *.zip"; - } - - @Override - public Icon getIcon() { - return AllIcons.General.SecondaryGroup; - } - - @NotNull - @Override - public AbstractModuleBuilder createModuleBuilder() { - return new EduCustomCourseModuleBuilder(); - } - - @Nullable - @Override - public ValidationInfo validateSettings() { - return null; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduLocalCoursePanel.form b/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduLocalCoursePanel.form deleted file mode 100644 index 19b885d3a4c6..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduLocalCoursePanel.form +++ /dev/null @@ -1,53 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduLocalCoursePanel.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduLocalCoursePanel.java deleted file mode 100644 index 790b2829865a..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/localCourses/EduLocalCoursePanel.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.jetbrains.edu.learning.intellij.localCourses; - -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; -import com.intellij.openapi.ui.TextFieldWithBrowseButton; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; -import com.jetbrains.edu.learning.ui.StudyNewProjectPanel; - -import javax.swing.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -public class EduLocalCoursePanel { - private final StudyProjectGenerator myGenerator; - private EduCustomCourseModuleBuilder myBuilder; - private JPanel myContentPanel; - private JPanel myInfoPanel; - private TextFieldWithBrowseButton myCourseArchivePath; - private StudyNewProjectPanel myStudyPanel; - - public EduLocalCoursePanel(final StudyProjectGenerator generator, EduCustomCourseModuleBuilder builder) { - myGenerator = generator; - myBuilder = builder; - myCourseArchivePath.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent actionEvent) { - String coursePath = myCourseArchivePath.getText(); - Course courseInfo = generator.addLocalCourse(coursePath); - if (courseInfo != null) { - myBuilder.setSelectedCourse(courseInfo); - myGenerator.setSelectedCourse(courseInfo); - myStudyPanel.updateInfoPanel(courseInfo); - } - else { - //TODO: implement validation - } - } - }); - myCourseArchivePath.addBrowseFolderListener("Select Course Archive", null, null, - FileChooserDescriptorFactory.createSingleFileDescriptor()); - } - - public JPanel getContentPanel() { - return myContentPanel; - } - - private void createUIComponents() { - myStudyPanel = new StudyNewProjectPanel(myGenerator, true); - myInfoPanel = myStudyPanel.getInfoPanel(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/stepik/EduRemoteCourseModuleBuilder.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/stepik/EduRemoteCourseModuleBuilder.java deleted file mode 100644 index db12a000ea32..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/stepik/EduRemoteCourseModuleBuilder.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.jetbrains.edu.learning.intellij.stepik; - -import com.intellij.ide.util.projectWizard.ModuleBuilder; -import com.intellij.ide.util.projectWizard.ModuleWizardStep; -import com.intellij.ide.util.projectWizard.SettingsStep; -import com.intellij.lang.Language; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.ModifiableModuleModel; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleType; -import com.intellij.openapi.module.ModuleWithNameAlreadyExists; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.util.InvalidDataException; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.intellij.generation.EduProjectGenerator; -import org.jdom.JDOMException; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; - -class EduRemoteCourseModuleBuilder extends ModuleBuilder { - private static final Logger LOG = Logger.getInstance(EduRemoteCourseModuleBuilder.class); - private final Course myCourse; - private final ModuleType myModuleType; - private final ModuleBuilder myModuleBuilder; - private EduProjectGenerator myGenerator = new EduProjectGenerator(); - - public EduRemoteCourseModuleBuilder(Course course) { - myCourse = course; - myModuleType = EduPluginConfigurator.INSTANCE.forLanguage(myCourse.getLanguageById()).getModuleType(); - myModuleBuilder = myModuleType.createModuleBuilder(); - } - - @Nullable - @Override - public Module commitModule(@NotNull Project project, @Nullable ModifiableModuleModel model) { - Module module = super.commitModule(project, model); - if (module == null) { - return null; - } - String languageName = myCourse.getLanguageID(); - Language language = Language.findLanguageByID(languageName); - if (language == null) { - return module; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); - if (configurator == null) { - return module; - } - configurator.configureModule(module); - return module; - } - - @Nullable - @Override - public String getBuilderId() { - return "remote.course.builder." + myCourse.getName(); - } - - @Nullable - @Override - public ModuleWizardStep modifySettingsStep(@NotNull SettingsStep settingsStep) { - return myModuleBuilder.modifyProjectTypeStep(settingsStep); - } - - @NotNull - @Override - public Module createModule(@NotNull ModifiableModuleModel moduleModel) - throws InvalidDataException, IOException, ModuleWithNameAlreadyExists, JDOMException, ConfigurationException { - Module baseModule = super.createModule(moduleModel); - String languageName = myCourse.getLanguageID(); - Language language = Language.findLanguageByID(languageName); - if (language != null) { - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); - if (configurator != null) { - Project project = baseModule.getProject(); - myGenerator.setSelectedCourse(myCourse); - myGenerator.generateProject(project, project.getBaseDir()); - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - LOG.info("failed to generate course"); - return baseModule; - } - configurator.createCourseModuleContent(moduleModel, project, course, getModuleFileDirectory()); - } - } - return baseModule; - } - - @Override - public void setupRootModel(ModifiableRootModel modifiableRootModel) throws ConfigurationException { - myModuleBuilder.setupRootModel(modifiableRootModel); - } - - @Override - public ModuleType getModuleType() { - return myModuleType; - } - - @Override - public void setContentEntryPath(String moduleRootPath) { - myModuleBuilder.setContentEntryPath(moduleRootPath); - super.setContentEntryPath(moduleRootPath); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/intellij/stepik/EduRemoteCourseTemplate.java b/python/educational-core/src/com/jetbrains/edu/learning/intellij/stepik/EduRemoteCourseTemplate.java deleted file mode 100644 index b3f1bd994e10..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/intellij/stepik/EduRemoteCourseTemplate.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jetbrains.edu.learning.intellij.stepik; - -import com.intellij.ide.util.projectWizard.AbstractModuleBuilder; -import com.intellij.openapi.ui.ValidationInfo; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.intellij.EduIntelliJProjectTemplate; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class EduRemoteCourseTemplate implements EduIntelliJProjectTemplate { - private final Course myCourse; - - public EduRemoteCourseTemplate(Course course) { - myCourse = course; - } - - @NotNull - @Override - public String getName() { - return myCourse.getName(); - } - - @Nullable - @Override - public String getDescription() { - return myCourse.getDescription(); - } - - @Override - public Icon getIcon() { - return EducationalCoreIcons.Stepik; - } - - @NotNull - @Override - public AbstractModuleBuilder createModuleBuilder() { - return new EduRemoteCourseModuleBuilder(myCourse); - } - - @Nullable - @Override - public ValidationInfo validateSettings() { - return null; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java b/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java deleted file mode 100644 index d5b42cd248eb..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/navigation/StudyNavigator.java +++ /dev/null @@ -1,230 +0,0 @@ -package com.jetbrains.edu.learning.navigation; - -import com.intellij.ide.projectView.ProjectView; -import com.intellij.ide.projectView.impl.AbstractProjectViewPane; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.ToolWindow; -import com.intellij.openapi.wm.ToolWindowId; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.util.ui.tree.TreeUtil; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.*; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.tree.TreePath; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class StudyNavigator { - private StudyNavigator() { - - } - - public static Task nextTask(@NotNull final Task task) { - Lesson currentLesson = task.getLesson(); - List taskList = currentLesson.getTaskList(); - if (task.getIndex() < taskList.size()) { - return taskList.get(task.getIndex()); - } - Lesson nextLesson = nextLesson(currentLesson); - if (nextLesson == null) { - return null; - } - List nextLessonTaskList = nextLesson.getTaskList(); - while (nextLessonTaskList.isEmpty()) { - nextLesson = nextLesson(nextLesson); - if (nextLesson == null) { - return null; - } - nextLessonTaskList = nextLesson.getTaskList(); - } - return StudyUtils.getFirst(nextLessonTaskList); - } - - public static Task previousTask(@NotNull final Task task) { - Lesson currentLesson = task.getLesson(); - int prevTaskIndex = task.getIndex() - 2; - if (prevTaskIndex >= 0) { - return currentLesson.getTaskList().get(prevTaskIndex); - } - Lesson prevLesson = previousLesson(currentLesson); - if (prevLesson == null) { - return null; - } - //getting last task in previous lesson - List prevLessonTaskList = prevLesson.getTaskList(); - while (prevLessonTaskList.isEmpty()) { - prevLesson = previousLesson(prevLesson); - if (prevLesson == null) { - return null; - } - prevLessonTaskList = prevLesson.getTaskList(); - } - return prevLessonTaskList.get(prevLessonTaskList.size() - 1); - } - - public static Lesson nextLesson(@NotNull final Lesson lesson) { - List lessons = lesson.getCourse().getLessons(); - int nextLessonIndex = lesson.getIndex(); - if (nextLessonIndex >= lessons.size()) { - return null; - } - return lessons.get(nextLessonIndex); - } - - public static Lesson previousLesson(@NotNull final Lesson lesson) { - int prevLessonIndex = lesson.getIndex() - 2; - if (prevLessonIndex < 0) { - return null; - } - return lesson.getCourse().getLessons().get(prevLessonIndex); - } - - public static void navigateToFirstFailedAnswerPlaceholder(@NotNull final Editor editor, @NotNull final TaskFile taskFile) { - final Project project = editor.getProject(); - if (project == null) return; - for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) { - if (answerPlaceholder.getStatus() != StudyStatus.Failed) { - continue; - } - navigateToAnswerPlaceholder(editor, answerPlaceholder); - break; - } - } - - public static void navigateToAnswerPlaceholder(@NotNull final Editor editor, @NotNull final AnswerPlaceholder answerPlaceholder) { - if (editor.isDisposed()) { - return; - } - Pair offsets = StudyUtils.getPlaceholderOffsets(answerPlaceholder, editor.getDocument()); - editor.getCaretModel().moveToOffset(offsets.first); - editor.getSelectionModel().setSelection(offsets.first, offsets.second); - } - - - public static void navigateToFirstAnswerPlaceholder(@NotNull final Editor editor, @NotNull final TaskFile taskFile) { - if (!taskFile.getActivePlaceholders().isEmpty()) { - AnswerPlaceholder firstAnswerPlaceholder = StudyUtils.getFirst(taskFile.getActivePlaceholders()); - if (firstAnswerPlaceholder == null) return; - navigateToAnswerPlaceholder(editor, firstAnswerPlaceholder); - } - } - - @Nullable - private static VirtualFile getFirstTaskFile(@NotNull final VirtualFile taskDir, @NotNull final Project project) { - for (VirtualFile virtualFile : taskDir.getChildren()) { - if (StudyUtils.getTaskFile(project, virtualFile) != null) { - return virtualFile; - } - } - return null; - } - - public static void navigateToTask(@NotNull final Project project, @NotNull final String lessonName, @NotNull final String taskName) { - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - final Lesson lesson = course.getLesson(lessonName); - if (lesson == null) { - return; - } - final Task task = lesson.getTask(taskName); - if (task == null) { - return; - } - ApplicationManager.getApplication().invokeLater(() -> navigateToTask(project, task)); - } - - public static void navigateToTask(@NotNull Project project, @NotNull Task task) { - for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) { - FileEditorManager.getInstance(project).closeFile(file); - } - Map taskFiles = task.getTaskFiles(); - VirtualFile taskDir = task.getTaskDir(project); - if (taskDir == null) { - return; - } - VirtualFile srcDir = taskDir.findChild(EduNames.SRC); - if (srcDir != null) { - taskDir = srcDir; - } - if (taskFiles.isEmpty()) { - ProjectView.getInstance(project).select(taskDir, taskDir, false); - return; - } - VirtualFile fileToActivate = getFirstTaskFile(taskDir, project); - for (Map.Entry entry : taskFiles.entrySet()) { - final TaskFile taskFile = entry.getValue(); - if (taskFile.getActivePlaceholders().isEmpty()) { - continue; - } - VirtualFile virtualFile = taskDir.findFileByRelativePath(entry.getKey()); - if (virtualFile == null) { - continue; - } - FileEditorManager.getInstance(project).openFile(virtualFile, true); - fileToActivate = virtualFile; - } - EduUsagesCollector.taskNavigation(); - if (fileToActivate != null) { - updateProjectView(project, fileToActivate); - } - - StudyUtils.selectFirstAnswerPlaceholder(StudyUtils.getSelectedStudyEditor(project), project); - ToolWindow runToolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.RUN); - if (runToolWindow != null) { - runToolWindow.hide(null); - } - } - - private static void updateProjectView(@NotNull Project project, @NotNull VirtualFile fileToActivate) { - AbstractProjectViewPane viewPane = ProjectView.getInstance(project).getCurrentProjectViewPane(); - if (viewPane == null) { - return; - } - JTree tree = viewPane.getTree(); - ProjectView.getInstance(project).selectCB(fileToActivate, fileToActivate, false).doWhenDone(() -> { - List paths = TreeUtil.collectExpandedPaths(tree); - List toCollapse = new ArrayList<>(); - TreePath selectedPath = tree.getSelectionPath(); - for (TreePath treePath : paths) { - if (treePath.isDescendant(selectedPath)) { - continue; - } - if (toCollapse.isEmpty()) { - toCollapse.add(treePath); - continue; - } - for (int i = 0; i < toCollapse.size(); i++) { - TreePath path = toCollapse.get(i); - if (treePath.isDescendant(path)) { - toCollapse.set(i, treePath); - } - else { - if (!path.isDescendant(treePath)) { - toCollapse.add(treePath); - } - } - } - } - for (TreePath path : toCollapse) { - tree.collapsePath(path); - tree.fireTreeCollapsed(path); - } - }); - FileEditorManager.getInstance(project).openFile(fileToActivate, true); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduBrowseCoursesAction.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduBrowseCoursesAction.java deleted file mode 100644 index 23b5bd6fd1d8..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduBrowseCoursesAction.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jetbrains.edu.learning.newproject; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.ui.DialogBuilder; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.util.PlatformUtils; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.newproject.ui.EduCoursesPanel; -import com.jetbrains.edu.learning.newproject.ui.EduCreateNewProjectDialog; -import icons.EducationalCoreIcons; - -public class EduBrowseCoursesAction extends AnAction { - public EduBrowseCoursesAction() { - super("Browse Courses", "Browse list of available courses", EducationalCoreIcons.Course); - } - - @Override - public void update(AnActionEvent e) { - final Presentation presentation = e.getPresentation(); - presentation.setEnabledAndVisible(PlatformUtils.isJetBrainsProduct()); - } - - @Override - public void actionPerformed(AnActionEvent e) { - EduCoursesPanel panel = new EduCoursesPanel(); - DialogBuilder dialogBuilder = new DialogBuilder().title("Select Course").centerPanel(panel); - dialogBuilder.addOkAction().setText("Join"); - panel.addCourseValidationListener(new EduCoursesPanel.CourseValidationListener() { - @Override - public void validationStatusChanged(boolean canStartCourse) { - dialogBuilder.setOkActionEnabled(canStartCourse); - } - }); - dialogBuilder.setOkOperation(() -> { - dialogBuilder.getDialogWrapper().close(DialogWrapper.OK_EXIT_CODE); - Course course = panel.getSelectedCourse(); - String location = panel.getLocationString(); - EduCreateNewProjectDialog.createProject(EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()).getEduCourseProjectGenerator(), course, location); - }); - dialogBuilder.show(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduCourseProjectGenerator.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduCourseProjectGenerator.java deleted file mode 100644 index 27aebd977d9e..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/newproject/EduCourseProjectGenerator.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2000-2017 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.newproject; - -import com.intellij.facet.ui.ValidationResult; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.LabeledComponent; -import com.intellij.platform.DirectoryProjectGenerator; -import com.jetbrains.edu.learning.courseFormat.Course; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public interface EduCourseProjectGenerator { - @NotNull - DirectoryProjectGenerator getDirectoryProjectGenerator(); - - @Nullable - Object getProjectSettings(); - - void setCourse(@NotNull Course course); - - ValidationResult validate(); - - boolean beforeProjectGenerated(); - - void afterProjectGenerated(@NotNull Project project); - - @Nullable - default LabeledComponent getLanguageSettingsComponent(@NotNull Course selectedCourse) { - return null; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.form b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.form deleted file mode 100644 index 07f3118fe022..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.form +++ /dev/null @@ -1,132 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.java deleted file mode 100644 index 81dbcff55160..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCoursesPanel.java +++ /dev/null @@ -1,462 +0,0 @@ -package com.jetbrains.edu.learning.newproject.ui; - -import com.intellij.icons.AllIcons; -import com.intellij.ide.impl.ProjectUtil; -import com.intellij.openapi.actionSystem.ActionToolbarPosition; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileChooser.FileChooser; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; -import com.intellij.openapi.ui.*; -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.platform.DirectoryProjectGenerator; -import com.intellij.ui.*; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBList; -import com.intellij.ui.components.JBScrollPane; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.ui.JBUI; -import com.intellij.util.ui.UIUtil; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudySettings; -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.RemoteCourse; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; -import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicUser; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.File; -import java.util.*; -import java.util.List; -import java.util.stream.Collectors; - -public class EduCoursesPanel extends JPanel { - private static final Set FEATURED_COURSES = ContainerUtil.newHashSet("Introduction to Python", "Adaptive Python"); - private static final JBColor LIST_COLOR = new JBColor(Gray.xFF, Gray.x39); - public static final Color COLOR = new Color(70, 130, 180, 70); - private static final Logger LOG = Logger.getInstance(EduCoursesPanel.class); - private JPanel myMainPanel; - private JPanel myCourseListPanel; - private JPanel myCoursePanel; - private JPanel myAdvancedSettingsPlaceholder; - private JPanel myAdvancedSettings; - private FilterComponent mySearchField; - private JEditorPane myDescriptionTextArea; - private JBLabel myCourseNameLabel; - private JPanel myTagsPanel; - private JBScrollPane myInfoScroll; - private JBLabel myErrorLabel; - private JSplitPane mySplitPane; - private JPanel mySplitPaneRoot; - private JBList myCoursesList; - private LabeledComponent myLocationField; - private List myCourses; - private List myListeners = new ArrayList<>(); - - public EduCoursesPanel() { - setLayout(new BorderLayout()); - add(myMainPanel, BorderLayout.CENTER); - initUI(); - } - - - private void initUI() { - GuiUtils.replaceJSplitPaneWithIDEASplitter(mySplitPaneRoot, true); - mySplitPane.setDividerLocation(0.5); - mySplitPane.setResizeWeight(0.5); - myCourseNameLabel.setBorder(JBUI.Borders.empty(20, 10, 5, 10)); - Font labelFont = UIUtil.getLabelFont(); - myCourseNameLabel.setFont(new Font(labelFont.getName(), Font.BOLD, JBUI.scaleFontSize(18.0f))); - myTagsPanel.setBorder(JBUI.Borders.empty(0, 10)); - myDescriptionTextArea.setBorder(JBUI.Borders.empty(20, 10, 10, 10)); - myDescriptionTextArea.setEditorKit(UIUtil.getHTMLEditorKit()); - myDescriptionTextArea.setEditable(false); - myDescriptionTextArea.setPreferredSize(JBUI.size(myCoursePanel.getPreferredSize())); - myInfoScroll.setBorder(null); - myCoursesList = new JBList<>(); - myCourses = getCourses(); - updateModel(myCourses, null); - myErrorLabel.setVisible(false); - myErrorLabel.setBorder(JBUI.Borders.empty(20, 10, 0, 0)); - - ListCellRendererWrapper renderer = new ListCellRendererWrapper() { - @Override - public void customize(JList list, Course value, int index, boolean selected, boolean hasFocus) { - setText(value.getName()); - DirectoryProjectGenerator generator = getGenerator(value); - if (generator != null) { - boolean isPrivate = value instanceof RemoteCourse && !((RemoteCourse)value).isPublic(); - setIcon(isPrivate ? getPrivateCourseIcon(generator.getLogo()) : generator.getLogo()); - setToolTipText(isPrivate ? "Private course" : ""); - } - } - - @NotNull - public LayeredIcon getPrivateCourseIcon(@Nullable Icon languageLogo) { - LayeredIcon icon = new LayeredIcon(2); - icon.setIcon(languageLogo, 0, 0, 0); - icon.setIcon(AllIcons.Ide.Readonly, 1, JBUI.scale(7), JBUI.scale(7)); - return icon; - } - }; - myCoursesList.setCellRenderer(new DefaultListCellRenderer() { - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - Component component = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - if (component instanceof JLabel) { - ((JLabel)component).setBorder(JBUI.Borders.empty(5, 0)); - } - return component; - } - }); - myLocationField = createLocationComponent(); - myCoursesList.addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - processSelectionChanged(); - } - }); - DefaultActionGroup group = new DefaultActionGroup(new AnAction("Import Course", "import local course", AllIcons.ToolbarDecorator.Import) { - @Override - public void actionPerformed(AnActionEvent e) { - final FileChooserDescriptor fileChooser = new FileChooserDescriptor(true, false, false, true, false, false) { - @Override - public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { - return file.isDirectory() || StudyUtils.isZip(file.getName()); - } - - @Override - public boolean isFileSelectable(VirtualFile file) { - return StudyUtils.isZip(file.getName()); - } - - }; - FileChooser.chooseFile(fileChooser, null, VfsUtil.getUserHomeDir(), - file -> { - String fileName = file.getPath(); - Course course = new StudyProjectGenerator().addLocalCourse(fileName); - if (course != null) { - myCourses.add(course); - updateModel(myCourses, course.getName()); - EduUsagesCollector.localCourseAdded(); - } else { - Messages.showErrorDialog("Selected archive doesn't contain a valid course", "Failed to Add Local Course"); - } - }); - } - }); - - ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(myCoursesList). - disableAddAction().disableRemoveAction().disableUpDownActions().setActionGroup(group).setToolbarPosition(ActionToolbarPosition.BOTTOM); - JPanel toolbarDecoratorPanel = toolbarDecorator.createPanel(); - toolbarDecoratorPanel.setBorder(null); - myCoursesList.setBorder(null); - myCourseListPanel.add(toolbarDecoratorPanel, BorderLayout.CENTER); - Border border = JBUI.Borders.customLine(OnePixelDivider.BACKGROUND, 1, 0, 1, 1); - myCoursePanel.setBorder(border); - myCourseListPanel.setBorder(JBUI.Borders.customLine(OnePixelDivider.BACKGROUND, 1, 1, 1, 1)); - HideableDecorator decorator = new HideableDecorator(myAdvancedSettingsPlaceholder, "Advanced Settings", false); - decorator.setContentComponent(myAdvancedSettings); - myAdvancedSettings.setBorder(JBUI.Borders.empty(0, IdeBorderFactory.TITLED_BORDER_INDENT, 5, 0)); - myCoursesList.setBackground(LIST_COLOR); - myDescriptionTextArea.setBackground(UIUtil.getPanelBackground()); - myAdvancedSettingsPlaceholder.setVisible(false); - myErrorLabel.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (!isLoggedIn() && myErrorLabel.isVisible()) { - ApplicationManager.getApplication().getMessageBus().connect().subscribe(StudySettings.SETTINGS_CHANGED, () -> { - StepicUser user = StudySettings.getInstance().getUser(); - if (user != null) { - ApplicationManager.getApplication().invokeLater(() -> { - Course selectedCourse = myCoursesList.getSelectedValue(); - myCourses = getCourses(); - updateModel(myCourses, selectedCourse.getName()); - myErrorLabel.setVisible(false); - notifyListeners(true); - }, ModalityState.any()); - } - }); - EduStepicConnector.doAuthorize(() -> StudyUtils.showOAuthDialog()); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - if (!isLoggedIn() && myErrorLabel.isVisible()) { - e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } - } - - @Override - public void mouseExited(MouseEvent e) { - if (!isLoggedIn() && myErrorLabel.isVisible()) { - e.getComponent().setCursor(Cursor.getDefaultCursor()); - } - } - }); - - processSelectionChanged(); - } - - private void processSelectionChanged() { - Course selectedCourse = myCoursesList.getSelectedValue(); - notifyListeners(canStartCourse(selectedCourse)); - if (selectedCourse != null) { - updateCourseInfoPanel(selectedCourse); - } - } - - private void updateCourseInfoPanel(Course selectedCourse) { - updateCourseDescriptionPanel(selectedCourse); - updateAdvancedSettings(selectedCourse); - if (!isLoggedIn()) { - myErrorLabel.setVisible(true); - myErrorLabel.setText( - UIUtil.toHtml("Log in to Stepik " + (selectedCourse.isAdaptive() ? "to start adaptive course" : "to see more courses"))); - myErrorLabel.setForeground((selectedCourse.isAdaptive() ? MessageType.ERROR : MessageType.WARNING).getTitleForeground()); - } - } - - @NotNull - private static List getCourses() { - return new StudyProjectGenerator().getCoursesUnderProgress(true, "Getting Available Courses", null); - } - - private static boolean isLoggedIn() { - return StudySettings.getInstance().getUser() != null; - } - - private void updateAdvancedSettings(Course selectedCourse) { - myAdvancedSettingsPlaceholder.setVisible(true); - myLocationField.getComponent().setText(nameToLocation(selectedCourse.getName())); - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(selectedCourse.getLanguageById()); - if (configurator == null) { - return; - } - EduCourseProjectGenerator generator = configurator.getEduCourseProjectGenerator(); - if (generator == null) { - return; - } - LabeledComponent component = generator.getLanguageSettingsComponent(selectedCourse); - myAdvancedSettings.removeAll(); - myAdvancedSettings.add(myLocationField, BorderLayout.NORTH); - if (component != null) { - myAdvancedSettings.add(component, BorderLayout.SOUTH); - UIUtil.mergeComponentsWithAnchor(myLocationField, component); - } - myAdvancedSettings.revalidate(); - myAdvancedSettings.repaint(); - } - - private void updateCourseDescriptionPanel(@Nullable Course selectedCourse) { - if (selectedCourse == null) { - myInfoScroll.setVisible(false); - myAdvancedSettingsPlaceholder.setVisible(false); - return; - } - myInfoScroll.setVisible(true); - String courseName = selectedCourse.getName(); - String description = selectedCourse.getDescription(); - myCourseNameLabel.setText(courseName); - myTagsPanel.removeAll(); - addTags(myTagsPanel, selectedCourse); - myTagsPanel.revalidate(); - myTagsPanel.repaint(); - StringBuilder builder = new StringBuilder(); - List authors = selectedCourse.getAuthors(); - if (!authors.isEmpty()) { - builder.append("Instructor"); - if (authors.size() > 1) { - builder.append("s"); - } - builder.append(": "); - List fullNames = getAuthorFullNames(authors); - builder.append(StringUtil.join(fullNames, ", ")); - builder.append("

"); - } - builder.append(description.replace("\n", "
")); - myDescriptionTextArea.setText(UIUtil.toHtml(builder.toString())); - } - - private static List getAuthorFullNames(List authors) { - return authors.stream(). - map(user -> StringUtil.join(Arrays.asList(user.getFirstName(), user.getLastName()), " ")) - .collect(Collectors.toList()); - } - - private static void addTags(JPanel tagsPanel, @NotNull Course course) { - for (String tag : getTags(course)) { - tagsPanel.add(createTagLabel(tag)); - } - } - - private static JLabel createTagLabel(String tagText) { - Border emptyBorder = JBUI.Borders.empty(3, 5); - JBLabel label = new JBLabel(tagText); - label.setOpaque(true); - label.setBorder(emptyBorder); - label.setBackground(new JBColor(COLOR, COLOR)); - return label; - } - - private static int getWeight(@NotNull Course course) { - String name = course.getName(); - if (FEATURED_COURSES.contains(name)) { - return FEATURED_COURSES.size() - 1 - new ArrayList<>(FEATURED_COURSES).indexOf(name); - } - return FEATURED_COURSES.size(); - } - - private void updateModel(List courses, @Nullable String courseToSelect) { - DefaultListModel listModel = new DefaultListModel<>(); - Collections.sort(courses, Comparator.comparingInt(EduCoursesPanel::getWeight)); - for (Course course : courses) { - listModel.addElement(course); - } - myCoursesList.setModel(listModel); - if (myCoursesList.getItemsCount() > 0) { - myCoursesList.setSelectedIndex(0); - } else { - updateCourseDescriptionPanel(null); - } - if (courseToSelect == null) { - return; - } - Course newCourseToSelect = myCourses.stream().filter(course -> course.getName().equals(courseToSelect)).findFirst().orElse(null); - if (newCourseToSelect != null ) { - myCoursesList.setSelectedValue(newCourseToSelect, true); - } - } - - public Course getSelectedCourse() { - return myCoursesList.getSelectedValue(); - } - - private void createUIComponents() { - mySearchField = new FilterComponent("Edu.NewCourse", 5, true) { - @Override - public void filter() { - String filter = getFilter(); - List filtered = new ArrayList<>(); - for (Course course : myCourses) { - if (accept(filter, course)) { - filtered.add(course); - } - } - updateModel(filtered, null); - } - }; - UIUtil.setBackgroundRecursively(mySearchField, UIUtil.getTextFieldBackground()); - } - - public boolean accept(String filter, Course course) { - if (filter.isEmpty()) { - return true; - } - filter = filter.toLowerCase(); - if (course.getName().toLowerCase().contains(filter)) { - return true; - } - for (String tag : getTags(course)) { - if (tag.toLowerCase().contains(filter)) { - return true; - } - } - for (String authorName : getAuthorFullNames(course.getAuthors())) { - if (authorName.toLowerCase().contains(filter)) { - return true; - } - } - return false; - } - - @Nullable - private static DirectoryProjectGenerator getGenerator(@NotNull Course course) { - EduCourseProjectGenerator projectGenerator = - EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()).getEduCourseProjectGenerator(); - if (projectGenerator == null) { - LOG.info("project generator is null, language: " + course.getLanguageById().getDisplayName()); - } - return projectGenerator == null ? null : projectGenerator.getDirectoryProjectGenerator(); - } - - private static LabeledComponent createLocationComponent() { - TextFieldWithBrowseButton field = new TextFieldWithBrowseButton(); - field.addBrowseFolderListener("Select Course Location", "Select course location", null, - FileChooserDescriptorFactory.createSingleFolderDescriptor()); - return LabeledComponent.create(field, "Location", BorderLayout.WEST); - } - - @NotNull - private static String nameToLocation(@NotNull String courseName) { - String name = FileUtil.sanitizeFileName(courseName); - return FileUtil.findSequentNonexistentFile(new File(ProjectUtil.getBaseDir()), name, "").getAbsolutePath(); - } - - public String getLocationString() { - return myLocationField.getComponent().getText(); - } - - @Override - public Dimension getPreferredSize() { - return JBUI.size(600, 400); - } - - public void addCourseValidationListener(CourseValidationListener listener) { - myListeners.add(listener); - listener.validationStatusChanged(canStartCourse(myCoursesList.getSelectedValue())); - } - - private void notifyListeners(boolean canStartCourse) { - for (CourseValidationListener listener : myListeners) { - listener.validationStatusChanged(canStartCourse); - } - } - - private static boolean canStartCourse(Course selectedCourse) { - if (selectedCourse == null) { - return false; - } - - if (isLoggedIn()) { - return true; - } - - return !selectedCourse.isAdaptive(); - } - - public interface CourseValidationListener { - void validationStatusChanged(boolean canStartCourse); - } - - private static List getTags(@NotNull Course course) { - List tags = new ArrayList<>(); - tags.add(course.getLanguageById().getDisplayName()); - if (course.isAdaptive()) { - tags.add(EduNames.ADAPTIVE); - } - return tags; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectDialog.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectDialog.java deleted file mode 100644 index fa8ed2da76db..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectDialog.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2000-2017 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.newproject.ui; - -import com.intellij.facet.ui.ValidationResult; -import com.intellij.ide.util.projectWizard.AbstractNewProjectStep; -import com.intellij.lang.Language; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.platform.DirectoryProjectGenerator; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class EduCreateNewProjectDialog extends DialogWrapper { - private static final Logger LOG = Logger.getInstance(EduCreateNewProjectDialog.class); - protected Project myProject; - protected Course myCourse; - private final EduCreateNewProjectPanel myPanel; - - public EduCreateNewProjectDialog() { - super(false); - setTitle("New Project"); - Project defaultProject = ProjectManager.getInstance().getDefaultProject(); - myPanel = new EduCreateNewProjectPanel(defaultProject, this); - setOKButtonText("Create"); - init(); - } - - @Nullable - @Override - protected JComponent createCenterPanel() { - return myPanel; - } - - public void setCourse(@Nullable Course course) { - myCourse = course; - String description = course != null ? course.getDescription() : ""; - myPanel.setDescription(description); - } - - @Override - protected void doOKAction() { - if (myCourse == null) { - myPanel.setError("Selected course is null"); - return; - } - Language language = myCourse.getLanguageById(); - if (language == null) { - String message = "Selected course don't have language"; - myPanel.setError(message); - LOG.warn(message); - return; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); - if (configurator == null) { - String message = "A configurator for the selected course not found"; - myPanel.setError(message); - LOG.warn(message + ": " + language); - return; - } - EduCourseProjectGenerator projectGenerator = configurator.getEduCourseProjectGenerator(); - ValidationResult result = projectGenerator.validate(); - if (!result.isOk()) { - myPanel.setError(result.getErrorMessage()); - return; - } - createProject(projectGenerator, myCourse, myPanel.getLocationPath()); - } - - public static void createProject(@NotNull EduCourseProjectGenerator projectGenerator, @NotNull Course course, @NotNull String location) { - projectGenerator.setCourse(course); - if (!projectGenerator.beforeProjectGenerated()) { - return; - } - DirectoryProjectGenerator directoryProjectGenerator = projectGenerator.getDirectoryProjectGenerator(); - Project createdProject = AbstractNewProjectStep.doGenerateProject(null, location, directoryProjectGenerator, projectGenerator.getProjectSettings()); - if (createdProject == null) { - return; - } - projectGenerator.afterProjectGenerated(createdProject); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.form b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.form deleted file mode 100644 index 7a771f682f0f..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.form +++ /dev/null @@ -1,71 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.java deleted file mode 100644 index 80ce60042095..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewProjectPanel.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2000-2017 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.newproject.ui; - -import com.intellij.icons.AllIcons; -import com.intellij.ide.impl.ProjectUtil; -import com.intellij.openapi.fileChooser.FileChooserDescriptor; -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapperPeer; -import com.intellij.openapi.ui.TextFieldWithBrowseButton; -import com.intellij.openapi.util.io.FileUtil; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; -import java.io.File; - -public class EduCreateNewProjectPanel extends JPanel { - private JPanel myPanel; - private TextFieldWithBrowseButton myLocationField; - private JLabel myErrorIcon; - private JLabel myErrorLabel; - private JTextPane myDescription; - - public EduCreateNewProjectPanel(@NotNull final Project project, @NotNull EduCreateNewProjectDialog dialog) { - setLayout(new BorderLayout()); - add(myPanel, BorderLayout.CENTER); - myErrorIcon.setIcon(AllIcons.Actions.Lightning); - resetError(); - String location = findSequentNonExistingUntitled().toString(); - myLocationField.setText(location); - final int index = location.lastIndexOf(File.separator); - if (index > 0) { - JTextField textField = myLocationField.getTextField(); - textField.select(index + 1, location.length()); - textField.putClientProperty(DialogWrapperPeer.HAVE_INITIAL_SELECTION, true); - } - - final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); - myLocationField.addBrowseFolderListener("Select Base Directory", - "Select base directory for the project", - project, - descriptor); - - myLocationField.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - String location = FileUtil.toSystemDependentName(myLocationField.getText()); - File file = new File(location); - if (!FileUtil.ensureCanCreateFile(file)) { - dialog.setOKActionEnabled(false); - setError("Invalid location"); - } else { - dialog.setOKActionEnabled(true); - resetError(); - } - } - }); - } - - @NotNull - protected File findSequentNonExistingUntitled() { - return FileUtil.findSequentNonexistentFile(new File(ProjectUtil.getBaseDir()), "course", ""); - } - - private void setState(boolean isVisible) { - myErrorIcon.setVisible(isVisible); - myErrorLabel.setVisible(isVisible); - } - - void setError(@NotNull String message) { - myErrorLabel.setText(message); - setState(true); - } - - public String getLocationPath() { - return FileUtil.toSystemDependentName(myLocationField.getText()); - } - - public void resetError() { - setState(false); - } - - public void setDescription(@NotNull String description) { - myDescription.setText(description); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewStepikProjectDialog.java b/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewStepikProjectDialog.java deleted file mode 100644 index 7db74ffde68d..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/newproject/ui/EduCreateNewStepikProjectDialog.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2000-2017 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.newproject.ui; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.stepic.EduStepicAuthorizedClient; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicUser; - -import java.io.IOException; - -import static com.jetbrains.edu.learning.StudyUtils.execCancelable; - -public class EduCreateNewStepikProjectDialog extends EduCreateNewProjectDialog { - private static final Logger LOG = Logger.getInstance(EduCreateNewStepikProjectDialog.class); - - public EduCreateNewStepikProjectDialog() { - super(); - } - - public EduCreateNewStepikProjectDialog(int courseId) { - this(); - - StepicUser user = EduStepicAuthorizedClient.getCurrentUser(); - Project defaultProject = ProjectManager.getInstance().getDefaultProject(); - ApplicationManager.getApplication().invokeAndWait(() -> - ProgressManager.getInstance() - .runProcessWithProgressSynchronously(() -> { - ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - execCancelable(() -> { - try { - Course course = EduStepicConnector.getCourseFromStepik(user, courseId); - if (course != null) { - setTitle("New Project - " + course.getName()); - } - - setCourse(course); - } - catch (IOException e) { - LOG.warn("Tried to create a project for course with id=" + courseId, e); - } - return null; - }); - }, "Getting Available Courses", true, defaultProject) - ); - } - - @Override - public void show() { - if (myCourse != null) { - super.show(); - } else { - doCancelAction(); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/projectView/CourseDirectoryNode.java b/python/educational-core/src/com/jetbrains/edu/learning/projectView/CourseDirectoryNode.java deleted file mode 100644 index 896c8192bbc2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/projectView/CourseDirectoryNode.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.jetbrains.edu.learning.projectView; - -import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDirectory; -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.StudyItem; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - - -public class CourseDirectoryNode extends StudyDirectoryNode { - @NotNull protected final Project myProject; - protected final ViewSettings myViewSettings; - protected final Course myCourse; - - public CourseDirectoryNode(@NotNull Project project, - PsiDirectory value, - ViewSettings viewSettings, - @NotNull Course course) { - super(project, value, viewSettings); - myProject = project; - myViewSettings = viewSettings; - myCourse = course; - } - - @Override - protected void updateImpl(PresentationData data) { - setPresentation(data, myCourse.getName(), EducationalCoreIcons.Course); - } - - @Nullable - public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { - Object value = childNode.getValue(); - if (value instanceof PsiDirectory) { - PsiDirectory directory = (PsiDirectory)value; - Lesson lesson = myCourse.getLesson(directory.getName()); - return lesson != null ? createChildDirectoryNode(lesson, directory) : null; - } - return null; - } - - @Override - public PsiDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) { - final List lessons = myCourse.getLessons(); - final Lesson lesson = (Lesson)item; - if (directory.getChildren().length > 0 && lessons.size() == 1) { - final List tasks = lesson.getTaskList(); - if (tasks.size() == 1) { - PsiDirectory taskDirectory = (PsiDirectory)directory.getChildren()[0]; - PsiDirectory srcDir = taskDirectory.findSubdirectory(EduNames.SRC); - if (srcDir != null) { - taskDirectory = srcDir; - } - return new TaskDirectoryNode(myProject, taskDirectory, myViewSettings, tasks.get(0)); - } - } - return new LessonDirectoryNode(myProject, directory, myViewSettings, lesson); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/projectView/DirectoryNode.java b/python/educational-core/src/com/jetbrains/edu/learning/projectView/DirectoryNode.java deleted file mode 100644 index 6f9d3857cd94..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/projectView/DirectoryNode.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.jetbrains.edu.learning.projectView; - -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class DirectoryNode extends StudyDirectoryNode { - - @NotNull protected final Project myProject; - protected final ViewSettings myViewSettings; - - public DirectoryNode(@NotNull Project project, - PsiDirectory value, - ViewSettings viewSettings) { - super(project, value, viewSettings); - myProject = project; - myViewSettings = viewSettings; - } - - @Override - public boolean canNavigate() { - return true; - } - - @Nullable - @Override - public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { - Object value = childNode.getValue(); - if (value instanceof PsiDirectory) { - return createChildDirectoryNode(null, (PsiDirectory)value); - } - if (value instanceof PsiElement) { - PsiFile psiFile = ((PsiElement) value).getContainingFile(); - if (psiFile == null) return null; - VirtualFile virtualFile = psiFile.getVirtualFile(); - if (virtualFile == null) { - return null; - } - return StudyUtils.getTaskFile(myProject, virtualFile) != null ? childNode : null; - } - return null; - } - - @Override - public PsiDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value) { - return new DirectoryNode(myProject, value, myViewSettings); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/projectView/LessonDirectoryNode.java b/python/educational-core/src/com/jetbrains/edu/learning/projectView/LessonDirectoryNode.java deleted file mode 100644 index a7f29c772bd2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/projectView/LessonDirectoryNode.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.jetbrains.edu.learning.projectView; - -import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiManager; -import com.intellij.ui.JBColor; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class LessonDirectoryNode extends StudyDirectoryNode { - @NotNull protected final Project myProject; - protected final ViewSettings myViewSettings; - @NotNull protected final Lesson myLesson; - - public LessonDirectoryNode(@NotNull Project project, - PsiDirectory value, - ViewSettings viewSettings, - @NotNull Lesson lesson) { - super(project, value, viewSettings); - myProject = project; - myViewSettings = viewSettings; - myLesson = lesson; - } - - @Override - protected void updateImpl(PresentationData data) { - StudyStatus status = myLesson.getStatus(); - boolean isSolved = status != StudyStatus.Solved; - JBColor color = isSolved ? JBColor.BLACK : LIGHT_GREEN; - Icon icon = isSolved ? EducationalCoreIcons.Lesson : EducationalCoreIcons.LessonCompl; - updatePresentation(data, myLesson.getName(), color, icon, null); - } - - @Override - public int getWeight() { - return myLesson.getIndex(); - } - - @Nullable - @Override - public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { - Object value = childNode.getValue(); - if (value instanceof PsiDirectory) { - PsiDirectory directory = (PsiDirectory)value; - Task task = myLesson.getTask(directory.getName()); - if (task == null) { - return null; - } - VirtualFile srcDir = directory.getVirtualFile().findChild(EduNames.SRC); - if (srcDir != null) { - directory = PsiManager.getInstance(myProject).findDirectory(srcDir); - if (directory == null) { - return null; - } - } - return createChildDirectoryNode(task, directory); - } - return null; - } - - @Override - public PsiDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory directory) { - return new TaskDirectoryNode(myProject, directory, myViewSettings, ((Task)item)); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java b/python/educational-core/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java deleted file mode 100644 index ae9415bf9a83..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/projectView/StudyDirectoryNode.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.jetbrains.edu.learning.projectView; - -import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDirectory; -import com.intellij.ui.JBColor; -import com.intellij.ui.SimpleTextAttributes; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; - -public abstract class StudyDirectoryNode extends PsiDirectoryNode { - protected static final JBColor LIGHT_GREEN = new JBColor(new Color(0, 134, 0), new Color(98, 150, 85)); - - public StudyDirectoryNode(@NotNull final Project project, - PsiDirectory value, - ViewSettings viewSettings) { - super(project, value, viewSettings); - } - - protected void setPresentation(PresentationData data, String name, Icon icon) { - data.setPresentableText(name); - data.setIcon(icon); - } - - protected static void updatePresentation(PresentationData data, String name, JBColor color, Icon icon, @Nullable String additionalInfo) { - data.clearText(); - data.addText(name, new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, color)); - if (additionalInfo != null) { - data.addText(" (" + additionalInfo + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); - } - data.setIcon(icon); - } - - @Override - protected boolean hasProblemFileBeneath() { - return false; - } - - @Nullable - public abstract AbstractTreeNode modifyChildNode(AbstractTreeNode childNode); - - public abstract PsiDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value); - -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java b/python/educational-core/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java deleted file mode 100644 index f0132da6ac8f..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/projectView/StudyTreeStructureProvider.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.jetbrains.edu.learning.projectView; - -import com.intellij.ide.projectView.TreeStructureProvider; -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode; -import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDirectory; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Collection; - -public class StudyTreeStructureProvider implements TreeStructureProvider, DumbAware { - @NotNull - @Override - public Collection modify(@NotNull AbstractTreeNode parent, - @NotNull Collection children, - ViewSettings settings) { - Project project = parent.getProject(); - if (project == null || !shouldModify(project)) { - return children; - } - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return children; - } - Collection modifiedNodes = new ArrayList<>(); - for (AbstractTreeNode node : children) { - if (parent instanceof ProjectViewProjectNode && node instanceof PsiDirectoryNode) { - modifiedNodes.add(createCourseNode(project, node, settings, course)); - continue; - } - if (parent instanceof StudyDirectoryNode) { - AbstractTreeNode modifiedNode = ((StudyDirectoryNode)parent).modifyChildNode(node); - if (modifiedNode != null) { - modifiedNodes.add(modifiedNode); - } - } - } - return modifiedNodes; - } - - @NotNull - protected CourseDirectoryNode createCourseNode(Project project, AbstractTreeNode node, ViewSettings settings, Course course) { - return new CourseDirectoryNode(project, ((PsiDirectory)node.getValue()), settings, course); - } - - protected boolean shouldModify(@NotNull final Project project) { - return StudyUtils.isStudentProject(project); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/projectView/TaskDirectoryNode.java b/python/educational-core/src/com/jetbrains/edu/learning/projectView/TaskDirectoryNode.java deleted file mode 100644 index da41836b0c51..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/projectView/TaskDirectoryNode.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.jetbrains.edu.learning.projectView; - -import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.projectView.ViewSettings; -import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.ui.JBColor; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.StudyItem; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class TaskDirectoryNode extends StudyDirectoryNode { - @NotNull protected final Project myProject; - protected final ViewSettings myViewSettings; - @NotNull protected final Task myTask; - - public TaskDirectoryNode(@NotNull Project project, - PsiDirectory value, - ViewSettings viewSettings, - @NotNull Task task) { - super(project, value, viewSettings); - myProject = project; - myViewSettings = viewSettings; - myTask = task; - } - - @Override - public int getWeight() { - return myTask.getIndex(); - } - - @Override - protected void updateImpl(PresentationData data) { - StudyStatus status = myTask.getStatus(); - String subtaskInfo = myTask instanceof TaskWithSubtasks ? getSubtaskInfo((TaskWithSubtasks)myTask) : null; - if (status == StudyStatus.Unchecked) { - updatePresentation(data, myTask.getName(), JBColor.BLACK, EducationalCoreIcons.Task, subtaskInfo); - return; - } - boolean isSolved = status == StudyStatus.Solved; - JBColor color = isSolved ? LIGHT_GREEN : JBColor.RED; - Icon icon = isSolved ? EducationalCoreIcons.TaskCompl : EducationalCoreIcons.TaskProbl; - updatePresentation(data, myTask.getName(), color, icon, subtaskInfo); - } - - private String getSubtaskInfo(TaskWithSubtasks task) { - int index = task.getActiveSubtaskIndex() + 1; - int subtasksNum = task.getLastSubtaskIndex() + 1; - return EduNames.SUBTASK + " " + index + "/" + subtasksNum; - } - - @Override - public boolean expandOnDoubleClick() { - return false; - } - - @Override - public boolean canNavigate() { - return true; - } - - @Override - public void navigate(boolean requestFocus) { - StudyNavigator.navigateToTask(myProject, myTask); - } - - @Nullable - @Override - public AbstractTreeNode modifyChildNode(AbstractTreeNode childNode) { - Object value = childNode.getValue(); - if (value instanceof PsiDirectory && !((PsiDirectory)value).getName().equals(EduNames.SRC)) { - return createChildDirectoryNode(null, (PsiDirectory)value); - } - if (value instanceof PsiElement) { - PsiFile psiFile = ((PsiElement) value).getContainingFile(); - if (psiFile == null) return null; - VirtualFile virtualFile = psiFile.getVirtualFile(); - if (virtualFile == null) { - return null; - } - return StudyUtils.getTaskFile(myProject, virtualFile) != null ? childNode : null; - } - return null; - } - - @Override - public PsiDirectoryNode createChildDirectoryNode(StudyItem item, PsiDirectory value) { - return new DirectoryNode(myProject, value, myViewSettings); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/settings/StudyConfigurable.java b/python/educational-core/src/com/jetbrains/edu/learning/settings/StudyConfigurable.java deleted file mode 100644 index 9e45e4f68ba9..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/settings/StudyConfigurable.java +++ /dev/null @@ -1,68 +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.settings; - -import com.intellij.openapi.options.CompositeConfigurable; -import com.intellij.openapi.options.ex.ConfigurableWrapper; -import com.intellij.openapi.ui.VerticalFlowLayout; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.util.List; - -public class StudyConfigurable extends CompositeConfigurable { - public static final String ID = "com.jetbrains.edu.learning.stepic.EduConfigurable"; - private final JPanel myMainPanel; - - public StudyConfigurable() { - myMainPanel = new JPanel(new VerticalFlowLayout()); - } - - @Nls - @Override - public String getDisplayName() { - return "Education"; - } - - @NotNull - @Override - public String getHelpTopic() { - return ID; - } - - @Nullable - @Override - public JComponent createComponent() { - myMainPanel.removeAll(); - for (int i = 0; i < getConfigurables().size(); i++) { - StudyOptionsProvider provider = getConfigurables().get(i); - JComponent component = provider.createComponent(); - if (component != null) { - myMainPanel.add(component); - } - } - return myMainPanel; - } - - @NotNull - @Override - protected List createConfigurables() { - return ConfigurableWrapper.createConfigurables(StudyOptionsProviderEP.EP_NAME); - } -} - diff --git a/python/educational-core/src/com/jetbrains/edu/learning/settings/StudyOptionsProvider.java b/python/educational-core/src/com/jetbrains/edu/learning/settings/StudyOptionsProvider.java deleted file mode 100644 index 79324b2a3f57..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/settings/StudyOptionsProvider.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.jetbrains.edu.learning.settings; - -import com.intellij.openapi.options.UnnamedConfigurable; - -public interface StudyOptionsProvider extends UnnamedConfigurable { -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/settings/StudyOptionsProviderEP.java b/python/educational-core/src/com/jetbrains/edu/learning/settings/StudyOptionsProviderEP.java deleted file mode 100644 index 5c43b97a7bf6..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/settings/StudyOptionsProviderEP.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.jetbrains.edu.learning.settings; - -import com.intellij.openapi.extensions.ExtensionPointName; -import com.intellij.openapi.options.ConfigurableEP; - -public class StudyOptionsProviderEP extends ConfigurableEP { - public static final ExtensionPointName - EP_NAME = ExtensionPointName.create("Edu.optionsProvider"); -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduStatistics.java b/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduStatistics.java deleted file mode 100644 index 6715fa413d04..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduStatistics.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jetbrains.edu.learning.statistics; - -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.RoamingType; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Storage; -import com.jetbrains.edu.learning.StudySerializationUtils; -import gnu.trove.TObjectIntHashMap; -import org.jdom.Element; -import org.jetbrains.annotations.Nullable; - -@State( - name = "Edu.Statistics", - storages = @Storage(value = "edu.stats.xml", roamingType = RoamingType.DISABLED) -) -public class EduStatistics implements PersistentStateComponent { - private static final String DESCRIPTORS = "descriptors"; - private TObjectIntHashMap myUsageDescriptors = new TObjectIntHashMap<>(); - - @Nullable - @Override - public Element getState() { - Element descriptors = new Element(DESCRIPTORS); - myUsageDescriptors.forEachEntry((a, b) -> { - StudySerializationUtils.Xml.addChildWithName(descriptors, a, b); - return true; - }); - return descriptors; - } - - @Override - public void loadState(Element state) { - for (Element element : state.getChildren()) { - String key = element.getAttributeValue(StudySerializationUtils.Xml.NAME); - Integer value = Integer.valueOf(element.getAttributeValue(StudySerializationUtils.Xml.VALUE)); - myUsageDescriptors.put(key, value); - } - } - - public TObjectIntHashMap getUsageDescriptors() { - return myUsageDescriptors; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java b/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java deleted file mode 100644 index 3b12016f3b40..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/statistics/EduUsagesCollector.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2000-2016 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.statistics; - -import com.intellij.internal.statistic.UsagesCollector; -import com.intellij.internal.statistic.beans.GroupDescriptor; -import com.intellij.internal.statistic.beans.UsageDescriptor; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.util.containers.hash.HashSet; -import gnu.trove.TObjectIntHashMap; -import org.jetbrains.annotations.NotNull; - -import java.util.Set; - -public class EduUsagesCollector extends UsagesCollector { - private static final String GROUP_ID = "educational"; - - public static void coursePreviewed() { - advanceKey("coursePreview"); - } - - public static void stepikAuthorization() { - advanceKey("stepikAuthorization"); - } - - public static void localCourseAdded() { - advanceKey("localCourseAdded"); - } - - public static void fileRefreshed() { - advanceKey("refreshFile"); - } - - public static void placeholdersFilled() { - advanceKey("fillPlaceholders"); - } - - public static void projectTypeCreated(@NotNull String projectTypeId) { - advanceKey("project.created." + projectTypeId); - } - - public static void projectTypeOpened(@NotNull String projectTypeId) { - advanceKey("project.opened." + projectTypeId); - } - - public static void taskChecked() { - advanceKey("checkTask"); - } - - public static void hintShown() { - advanceKey("showHint"); - } - - public static void taskNavigation() { - advanceKey("navigateToTask"); - } - - public static void courseUploaded() { - advanceKey("uploadCourse"); - } - - public static void createdCourseArchive() { - advanceKey("courseArchive"); - } - - @NotNull - @Override - public Set getUsages() { - HashSet descriptors = new HashSet<>(); - getDescriptors().forEachEntry((key, value) -> { - descriptors.add(new UsageDescriptor(key, value)); - return true; - }); - getDescriptors().clear(); - return descriptors; - } - - - @NotNull - @Override - public GroupDescriptor getGroupId() { - return GroupDescriptor.create(GROUP_ID); - } - - private static void advanceKey(@NotNull String key) { - TObjectIntHashMap descriptors = getDescriptors(); - int oldValue = descriptors.get(key); - descriptors.put(key, oldValue + 1); - } - - private static TObjectIntHashMap getDescriptors() { - return ServiceManager.getService(EduStatistics.class).getUsageDescriptors(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java deleted file mode 100644 index 54dbb355cb07..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ /dev/null @@ -1,761 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.google.common.collect.ImmutableMap; -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.internal.LinkedTreeMap; -import com.intellij.ide.projectView.ProjectView; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageCommenters; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileTypes.LanguageFileType; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiManager; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudySettings; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.actions.StudyCheckAction; -import com.jetbrains.edu.learning.checker.StudyCheckResult; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.*; -import com.jetbrains.edu.learning.courseFormat.tasks.*; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import com.jetbrains.edu.learning.ui.StudyToolWindow; -import org.apache.http.HttpEntity; -import org.apache.http.HttpStatus; -import org.apache.http.client.config.RequestConfig; -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.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.*; -import java.util.concurrent.TimeUnit; - -import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getStep; - -public class EduAdaptiveStepicConnector { - public static final String PYCHARM_COMMENT = " Posted from PyCharm Edu\n"; - public static final int NEXT_RECOMMENDATION_REACTION = 2; - public static final int TOO_HARD_RECOMMENDATION_REACTION = 0; - public static final int TOO_BORING_RECOMMENDATION_REACTION = -1; - public static final String LOADING_NEXT_RECOMMENDATION = "Loading Next Recommendation"; - private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class); - private static final int CONNECTION_TIMEOUT = 60 * 1000; - - @Nullable - public static Task getNextRecommendation(@NotNull Project project, @NotNull RemoteCourse course) { - try { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) { - LOG.warn("Http client is null"); - return null; - } - - StepicUser user = StudySettings.getInstance().getUser(); - if (user == null) { - LOG.warn("User is null"); - return null; - } - - final URI uri = new URIBuilder(EduStepicNames.STEPIC_API_URL + EduStepicNames.RECOMMENDATIONS_URL) - .addParameter(EduNames.COURSE, String.valueOf(course.getId())) - .build(); - final HttpGet request = new HttpGet(uri); - setTimeout(request); - - final CloseableHttpResponse response = client.execute(request); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - - final int statusCode = response.getStatusLine().getStatusCode(); - EntityUtils.consume(responseEntity); - if (statusCode == 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 = EduStepicAuthorizedClient.getFromStepic(EduStepicNames.LESSONS + lessonId, - StepicWrappers.LessonContainer.class); - if (lessonContainer != null && lessonContainer.lessons.size() == 1) { - final Lesson realLesson = lessonContainer.lessons.get(0); - course.getLessons().get(0).setId(Integer.parseInt(lessonId)); - - for (int stepId : realLesson.steps) { - StepicWrappers.StepSource step = getStep(stepId); - String stepType = step.block.name; - StepikTaskBuilder taskBuilder = new StepikTaskBuilder(course, realLesson.getName(), step, stepId, user.getId()); - if (taskBuilder.isSupported(stepType)) { - final Task taskFromStep = taskBuilder.createTask(stepType); - if (taskFromStep != null) return taskFromStep; - } - else { - return skipRecommendation(project, course, user, lessonId); - } - } - } - else { - LOG.warn("Got unexpected number of lessons: " + (lessonContainer == null ? null : lessonContainer.lessons.size())); - } - } - else { - LOG.warn("Got empty recommendation for the task: " + responseString); - } - } - else { - throw new IOException("Stepic returned non 200 status code: " + responseString); - } - } - catch (IOException e) { - LOG.warn(e.getMessage()); - ApplicationManager.getApplication() - .invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Connection problems, Please, try again")); - } - catch (URISyntaxException e) { - LOG.warn(e.getMessage()); - } - return null; - } - - private static Task skipRecommendation(@NotNull Project project, @NotNull RemoteCourse course, StepicUser user, String lessonId) { - postRecommendationReaction(lessonId, String.valueOf(user.getId()), TOO_HARD_RECOMMENDATION_REACTION); - return getNextRecommendation(project, course); - } - - @Nullable - private static StepicWrappers.AdaptiveAttemptWrapper.Attempt getAttemptForStep(int stepId, int userId) { - try { - final List attempts = getAttempts(stepId, userId); - if (attempts != null && attempts.size() > 0) { - final StepicWrappers.AdaptiveAttemptWrapper.Attempt attempt = attempts.get(0); - return attempt.isActive() ? attempt : createNewAttempt(stepId); - } - else { - return createNewAttempt(stepId); - } - } - catch (URISyntaxException | IOException e) { - LOG.warn(e.getMessage()); - } - return null; - } - - private static StepicWrappers.AdaptiveAttemptWrapper.Attempt createNewAttempt(int id) throws IOException { - final String response = EduStepicConnector.postAttempt(id); - final StepicWrappers.AdaptiveAttemptContainer attempt = new Gson().fromJson(response, StepicWrappers.AdaptiveAttemptContainer.class); - return attempt.attempts.get(0); - } - - @Nullable - private static List getAttempts(int stepId, int userId) - throws URISyntaxException, IOException { - final URI attemptUrl = new URIBuilder(EduStepicNames.ATTEMPTS) - .addParameter("step", String.valueOf(stepId)) - .addParameter("user", String.valueOf(userId)) - .build(); - final StepicWrappers.AdaptiveAttemptContainer attempt = - EduStepicAuthorizedClient.getFromStepic(attemptUrl.toString(), StepicWrappers.AdaptiveAttemptContainer.class); - return attempt == null ? null : attempt.attempts; - } - - private static void setTimeout(HttpGet request) { - final RequestConfig requestConfig = RequestConfig.custom() - .setConnectionRequestTimeout(CONNECTION_TIMEOUT) - .setConnectTimeout(CONNECTION_TIMEOUT) - .setSocketTimeout(CONNECTION_TIMEOUT) - .build(); - request.setConfig(requestConfig); - } - - private static void setTimeout(HttpPost request) { - final RequestConfig requestConfig = RequestConfig.custom() - .setConnectionRequestTimeout(CONNECTION_TIMEOUT) - .setConnectTimeout(CONNECTION_TIMEOUT) - .setSocketTimeout(CONNECTION_TIMEOUT) - .build(); - request.setConfig(requestConfig); - } - - public static boolean postRecommendationReaction(@NotNull String lessonId, @NotNull String user, int reaction) { - final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.RECOMMENDATION_REACTIONS_URL); - final String json = new Gson() - .toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId))); - post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON)); - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) return false; - setTimeout(post); - try { - final CloseableHttpResponse execute = client.execute(post); - final int statusCode = execute.getStatusLine().getStatusCode(); - final HttpEntity entity = execute.getEntity(); - final String entityString = EntityUtils.toString(entity); - EntityUtils.consume(entity); - if (statusCode == HttpStatus.SC_CREATED) { - return true; - } - else { - LOG.warn("Stepic returned non-201 status code: " + statusCode + " " + entityString); - return false; - } - } - catch (IOException e) { - LOG.warn(e.getMessage()); - return false; - } - } - - public static void addNextRecommendedTask(@NotNull Project project, - @NotNull Lesson lesson, - @NotNull ProgressIndicator indicator, - int reactionToPost) { - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (!(course instanceof RemoteCourse)) { - LOG.warn("Course is in incorrect state"); - ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, - "Can't get next recommendation: course is broken")); - return; - } - - indicator.checkCanceled(); - final StepicUser user = StudySettings.getInstance().getUser(); - if (user == null) { - LOG.warn("Can't get next recommendation: user is null"); - ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, - "Can't get next recommendation: you're not logged in")); - return; - } - - final boolean reactionPosted = postRecommendationReaction(String.valueOf(lesson.getId()), String.valueOf(user.getId()), reactionToPost); - if (!reactionPosted) { - LOG.warn("Recommendation reaction wasn't posted"); - ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, "Couldn't post your reactionToPost")); - return; - } - - indicator.checkCanceled(); - final Task task = getNextRecommendation(project, (RemoteCourse)course); - if (task == null) { - ApplicationManager.getApplication().invokeLater(() -> StudyUtils.showErrorPopupOnToolbar(project, - "Couldn't load a new recommendation")); - return; - } - - task.initTask(lesson, false); - boolean replaceCurrentTask = reactionToPost == TOO_HARD_RECOMMENDATION_REACTION || reactionToPost == TOO_BORING_RECOMMENDATION_REACTION; - if (replaceCurrentTask) { - replaceCurrentTask(project, task, lesson); - } - else { - addAsNextTask(project, task, lesson); - } - - ApplicationManager.getApplication().invokeLater(() -> { - VirtualFileManager.getInstance().refreshWithoutFileWatcher(false); - ProjectView.getInstance(project).refresh(); - StudyNavigator.navigateToTask(project, task); - }); - } - - private static void addAsNextTask(@NotNull Project project, @NotNull Task task, @NotNull Lesson lesson) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - assert course != null; - - lesson.addTask(task); - task.setIndex(lesson.getTaskList().size()); - lesson.initLesson(course, true); - - final String lessonName = EduNames.LESSON + lesson.getIndex(); - createFilesForNewTask(project, task, lessonName, course.getLanguageById()); - } - - private static void createFilesForNewTask(@NotNull Project project, - @NotNull Task task, - @NotNull String lessonName, - @NotNull Language language) { - final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName); - if (lessonDir == null) { - return; - } - - ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { - PsiDirectory directory = PsiManager.getInstance(project).findDirectory(lessonDir); - if (directory == null) { - return; - } - - EduPluginConfigurator.INSTANCE.forLanguage(language).createTaskContent(project, task, null, directory, task.getLesson().getCourse()); - })); - } - - public static void replaceCurrentTask(@NotNull Project project, @NotNull Task task, @NotNull Lesson lesson) { - Course course = StudyTaskManager.getInstance(project).getCourse(); - assert course != null; - - int taskIndex = lesson.getTaskList().size(); - - task.setIndex(taskIndex); - lesson.getTaskList().set(taskIndex - 1, task); - - final String lessonName = EduNames.LESSON + lesson.getIndex(); - updateProjectFiles(project, task, lessonName, course.getLanguageById()); - setToolWindowText(project, task); - } - - private static void updateProjectFiles(@NotNull Project project, @NotNull Task task, @NotNull String lessonName, Language language) { - final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName); - if (lessonDir == null) { - return; - } - - ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { - try { - PsiDirectory directory = PsiManager.getInstance(project).findDirectory(lessonDir); - if (directory == null) { - return; - } - - removeOldProjectFiles(lessonDir, task.getIndex()); - EduPluginConfigurator.INSTANCE.forLanguage(language).createTaskContent(project, task, null, directory, task.getLesson().getCourse()); - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - })); - } - - private static void removeOldProjectFiles(@NotNull VirtualFile lessonDir, int taskIndex) throws IOException { - final VirtualFile taskDir = lessonDir.findChild(EduNames.TASK + taskIndex); - if (taskDir == null) { - LOG.warn("Failed to update files for a new recommendation: task directory is null"); - return; - } - - taskDir.delete(EduAdaptiveStepicConnector.class); - } - - private static void setToolWindowText(@NotNull Project project, @NotNull Task task) { - final StudyToolWindow window = StudyUtils.getStudyToolWindow(project); - if (window != null) { - window.setTaskText(task.getTaskDescription(), project); - } - } - - private static String getCodeTemplateForTask(@NotNull Language language, - @Nullable LinkedTreeMap codeTemplates) { - if (codeTemplates != null) { - final String languageString = EduPluginConfigurator.INSTANCE.forLanguage(language).getStepikDefaultLanguage(); - return (String)codeTemplates.get(languageString); - } - - return null; - } - - public static StudyCheckResult checkChoiceTask(@NotNull ChoiceTask task, @NotNull StepicUser user) { - if (task.getSelectedVariants().isEmpty()) return new StudyCheckResult(StudyStatus.Failed, "No variants selected"); - final StepicWrappers.AdaptiveAttemptWrapper.Attempt attempt = getAttemptForStep(task.getStepId(), user.getId()); - - if (attempt != null) { - final int attemptId = attempt.id; - - final boolean isActiveAttempt = task.getSelectedVariants().stream() - .allMatch(index -> attempt.dataset.options.get(index).equals(task.getChoiceVariants().get(index))); - if (!isActiveAttempt) return new StudyCheckResult(StudyStatus.Failed, "Your solution is out of date. Please try again"); - final StepicWrappers.SubmissionToPostWrapper wrapper = new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), - createChoiceTaskAnswerArray(task)); - final StudyCheckResult result = doAdaptiveCheck(wrapper, attemptId, user.getId()); - if (result.getStatus() == StudyStatus.Failed) { - try { - createNewAttempt(task.getStepId()); - StepicWrappers.StepSource step = getStep(task.getStepId()); - StepikTaskBuilder taskBuilder = new StepikTaskBuilder((RemoteCourse)task.getLesson().getCourse(), task.getName(), - step, task.getStepId(), user.getId()); - final Task updatedTask = taskBuilder.createTask(step.block.name); - if (updatedTask instanceof ChoiceTask) { - final List variants = ((ChoiceTask)updatedTask).getChoiceVariants(); - task.setChoiceVariants(variants); - task.setSelectedVariants(new ArrayList<>()); - } - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - } - return result; - } - - return new StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH); - } - - private static boolean[] createChoiceTaskAnswerArray(@NotNull ChoiceTask task) { - final List selectedVariants = task.getSelectedVariants(); - final boolean[] answer = new boolean[task.getChoiceVariants().size()]; - for (Integer index : selectedVariants) { - answer[index] = true; - } - return answer; - } - - public static StudyCheckResult checkCodeTask(@NotNull Project project, @NotNull Task task, @NotNull StepicUser user) { - int attemptId = -1; - try { - attemptId = getAttemptId(task); - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - if (attemptId != -1) { - Course course = task.getLesson().getCourse(); - Language courseLanguage = course.getLanguageById(); - final Editor editor = StudyUtils.getSelectedEditor(project); - if (editor != null) { - String commentPrefix = LanguageCommenters.INSTANCE.forLanguage(courseLanguage).getLineCommentPrefix(); - final String answer = commentPrefix + PYCHARM_COMMENT + editor.getDocument().getText(); - String defaultLanguage = EduPluginConfigurator.INSTANCE.forLanguage(courseLanguage).getStepikDefaultLanguage(); - final StepicWrappers.SubmissionToPostWrapper submissionToPost = - new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), defaultLanguage, answer); - return doAdaptiveCheck(submissionToPost, attemptId, user.getId()); - } - } - else { - LOG.warn("Got an incorrect attempt id: " + attemptId); - } - return new StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH); - } - - private static StudyCheckResult doAdaptiveCheck(@NotNull StepicWrappers.SubmissionToPostWrapper submission, - int attemptId, int userId) { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client != null) { - StepicWrappers.ResultSubmissionWrapper wrapper = postResultsForCheck(client, submission); - if (wrapper != null) { - wrapper = getCheckResults(client, wrapper, attemptId, userId); - if (wrapper.submissions.length > 0) { - final String status = wrapper.submissions[0].status; - final String hint = wrapper.submissions[0].hint; - final boolean isSolved = !status.equals("wrong"); - return new StudyCheckResult(isSolved ? StudyStatus.Solved : StudyStatus.Failed, hint.isEmpty() ? StringUtil.capitalize(status) + " solution" : hint); - } - else { - LOG.warn("Got a submission wrapper with incorrect submissions number: " + wrapper.submissions.length); - } - } - else { - LOG.warn("Can't do adaptive check: " + "wrapper is null"); - return new StudyCheckResult(StudyStatus.Unchecked, "Can't get check results for Stepik"); - } - } - return new StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH); - } - - @Nullable - private static StepicWrappers.ResultSubmissionWrapper postResultsForCheck(@NotNull final CloseableHttpClient client, - @NotNull StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper) { - final CloseableHttpResponse response; - try { - final HttpPost httpPost = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.SUBMISSIONS); - setTimeout(httpPost); - try { - httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper))); - } - catch (UnsupportedEncodingException e) { - LOG.warn(e.getMessage()); - } - response = client.execute(httpPost); - final HttpEntity entity = response.getEntity(); - final String entityString = EntityUtils.toString(entity); - EntityUtils.consume(entity); - return new Gson().fromJson(entityString, StepicWrappers.ResultSubmissionWrapper.class); - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - return null; - } - - @NotNull - private static StepicWrappers.ResultSubmissionWrapper getCheckResults(@NotNull CloseableHttpClient client, - @NotNull StepicWrappers.ResultSubmissionWrapper wrapper, - int attemptId, - int userId) { - try { - while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) { - TimeUnit.MILLISECONDS.sleep(500); - final URI submissionURI = new URIBuilder(EduStepicNames.STEPIC_API_URL + EduStepicNames.SUBMISSIONS) - .addParameter("attempt", String.valueOf(attemptId)) - .addParameter("order", "desc") - .addParameter("user", String.valueOf(userId)) - .build(); - final HttpGet httpGet = new HttpGet(submissionURI); - setTimeout(httpGet); - final CloseableHttpResponse httpResponse = client.execute(httpGet); - final HttpEntity entity = httpResponse.getEntity(); - final String entityString = EntityUtils.toString(entity); - EntityUtils.consume(entity); - wrapper = new Gson().fromJson(entityString, StepicWrappers.ResultSubmissionWrapper.class); - } - } - catch (InterruptedException | URISyntaxException | IOException e) { - LOG.warn(e.getMessage()); - } - return wrapper; - } - - private static int getAttemptId(@NotNull Task task) throws IOException { - final StepicWrappers.AdaptiveAttemptWrapper attemptWrapper = new StepicWrappers.AdaptiveAttemptWrapper(task.getStepId()); - - final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.ATTEMPTS); - post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper))); - - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) return -1; - setTimeout(post); - final CloseableHttpResponse httpResponse = client.execute(post); - final int statusCode = httpResponse.getStatusLine().getStatusCode(); - final HttpEntity entity = httpResponse.getEntity(); - final String entityString = EntityUtils.toString(entity); - EntityUtils.consume(entity); - if (statusCode == HttpStatus.SC_CREATED) { - final StepicWrappers.AttemptContainer container = - new Gson().fromJson(entityString, StepicWrappers.AttemptContainer.class); - return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1; - } - return -1; - } - - private static void createTestFileFromSamples(@NotNull Task task, - @NotNull List> 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); - } - - @NotNull - public static List getEnrolledCoursesIds(@NotNull StepicUser stepicUser) { - try { - final URI enrolledCoursesUri = new URIBuilder(EduStepicNames.COURSES).addParameter("enrolled", "true").build(); - final List courses = EduStepicAuthorizedClient.getFromStepic(enrolledCoursesUri.toString(), - StepicWrappers.CoursesContainer.class, - stepicUser).courses; - final ArrayList ids = new ArrayList<>(); - for (RemoteCourse course : courses) { - ids.add(course.getId()); - } - return ids; - } - catch (IOException | URISyntaxException e) { - LOG.warn(e.getMessage()); - } - return Collections.emptyList(); - } - - public static String wrapAdaptiveCourseText(Task task, @NotNull String text) { - String finalText = text; - if (task instanceof TheoryTask) { - finalText += "\n\nNote: This theory task aims to help you solve difficult tasks. " + - "Please, read it and press \"Check\" to go further."; - } - else if (!(task instanceof ChoiceTask)) { - finalText += "\n\nNote: Use standard input to obtain input for the task."; - } - finalText += getFooterWithLink(task); - - return finalText; - } - - @NotNull - private static String getFooterWithLink(Task task) { - return - "
" + "Open on Stepik" + "
"; - } - - private static class StepikTaskBuilder { - private static final String TASK_NAME = "task"; - private int myStepId; - private int myUserId; - private final String myName; - private final Language myLanguage; - private StepicWrappers.Step myStep; - private final Map> taskTypes = ImmutableMap.of( - "code", () -> codeTask(), - "choice", () -> choiceTask(), - "text", () -> theoryTask(), - "task", () -> pycharmTask() - ); - - public StepikTaskBuilder(@NotNull RemoteCourse course, - @NotNull String name, - @NotNull StepicWrappers.StepSource step, - int stepId, int userId) { - myName = name; - myStep = step.block; - myStepId = stepId; - myUserId = userId; - myLanguage = course.getLanguageById(); - } - - @Nullable - public Task createTask(String type) { - return taskTypes.get(type).compute(); - } - - public boolean isSupported(String type) { - return taskTypes.containsKey(type); - } - - @NotNull - private CodeTask codeTask() { - CodeTask task = new CodeTask(myName); - task.setStepId(myStepId); - - task.setStatus(StudyStatus.Unchecked); - final StringBuilder taskDescription = new StringBuilder(myStep.text); - if (myStep.options.samples != null) { - taskDescription.append("
"); - for (List sample : myStep.options.samples) { - if (sample.size() == 2) { - taskDescription.append("Sample Input:
"); - taskDescription.append(StringUtil.replace(sample.get(0), "\n", "
")); - taskDescription.append("
"); - taskDescription.append("Sample Output:
"); - taskDescription.append(StringUtil.replace(sample.get(1), "\n", "
")); - taskDescription.append("

"); - } - } - } - - if (myStep.options.executionMemoryLimit != null && myStep.options.executionTimeLimit != null) { - taskDescription.append("
").append("Memory limit: ").append(myStep.options.executionMemoryLimit).append(" Mb") - .append("
") - .append("Time limit: ").append(myStep.options.executionTimeLimit).append("s").append("

"); - } - task.addTaskText(EduNames.TASK, taskDescription.toString()); - - if (myStep.options.test != null) { - for (StepicWrappers.FileWrapper wrapper : myStep.options.test) { - task.addTestsTexts(wrapper.name, wrapper.text); - } - } - else { - if (myLanguage.isKindOf("Python") && myStep.options.samples != null) { - createTestFileFromSamples(task, myStep.options.samples); - } - } - - task.taskFiles = new HashMap<>(); - if (myStep.options.files != null) { - for (TaskFile taskFile : myStep.options.files) { - task.taskFiles.put(taskFile.name, taskFile); - } - } - else { - final String templateForTask = getCodeTemplateForTask(myLanguage, myStep.options.codeTemplates); - String commentPrefix = LanguageCommenters.INSTANCE.forLanguage(myLanguage).getLineCommentPrefix(); - String text = templateForTask == null ? (commentPrefix + " write your answer here \n") : templateForTask; - String taskFileName = getTaskFileName(myLanguage); - if (taskFileName != null) { - createMockTaskFile(task, text, taskFileName); - } - } - return task; - } - - @NotNull - private ChoiceTask choiceTask() { - ChoiceTask task = new ChoiceTask(myName); - task.setStepId(myStepId); - task.addTaskText(EduNames.TASK, myStep.text); - - final StepicWrappers.AdaptiveAttemptWrapper.Attempt attempt = getAttemptForStep(myStepId, myUserId); - if (attempt != null) { - final StepicWrappers.AdaptiveAttemptWrapper.Dataset dataset = attempt.dataset; - if (dataset != null) { - task.setChoiceVariants(dataset.options); - task.setMultipleChoice(dataset.is_multiple_choice); - } - else { - LOG.warn("Dataset for step " + myStepId + " is null"); - } - } - String commentPrefix = LanguageCommenters.INSTANCE.forLanguage(myLanguage).getLineCommentPrefix(); - String taskFileName = getTaskFileName(myLanguage); - if (taskFileName != null) { - createMockTaskFile(task, commentPrefix + " you can experiment here, it won't be checked", taskFileName); - } - - return task; - } - - @NotNull - private TheoryTask theoryTask() { - TheoryTask task = new TheoryTask(myName); - task.setStepId(myStepId); - task.addTaskText(EduNames.TASK, myStep.text); - String commentPrefix = LanguageCommenters.INSTANCE.forLanguage(myLanguage).getLineCommentPrefix(); - String taskFileName = getTaskFileName(myLanguage); - - if (taskFileName != null) { - createMockTaskFile(task, commentPrefix + " this is a theory task. You can use this editor as a playground", taskFileName); - } - return task; - } - - @Nullable - private PyCharmTask pycharmTask() { - try { - return (PyCharmTask)EduStepicConnector.createTask(myStepId); - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - - return null; - } - - private static void createMockTaskFile(@NotNull Task task, @NotNull String editorText, @NotNull String taskFileName) { - final TaskFile taskFile = new TaskFile(); - taskFile.text = editorText; - taskFile.name = taskFileName; - task.taskFiles.put(taskFile.name, taskFile); - } - - @Nullable - private static String getTaskFileName(@NotNull Language language) { - // This is a hacky way to how we should name task file. - // It's assumed that if test's name is capitalized we need to capitalize task file name too. - String testFileName = EduPluginConfigurator.INSTANCE.forLanguage(language).getTestFileName(); - boolean capitalize = !testFileName.isEmpty() && Character.isUpperCase(testFileName.charAt(0)); - - LanguageFileType type = language.getAssociatedFileType(); - if (type == null) { - LOG.warn("Failed to create task file name: associated file type for " + language + " is null"); - return null; - } - - return (capitalize ? StringUtil.capitalize(TASK_NAME) : TASK_NAME) + "." + type.getDefaultExtension(); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduCourseUpdater.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduCourseUpdater.java deleted file mode 100644 index 0a7721488be4..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduCourseUpdater.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2017 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.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ApplicationComponent; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; - -import java.util.ArrayList; -import java.util.List; - -public class EduCourseUpdater implements ApplicationComponent { - - private List ourRemoteCourses = new ArrayList<>(); - - public List getRemoteCourses() { - return ourRemoteCourses; - } - - public static EduCourseUpdater getInstance() { - return ApplicationManager.getApplication().getComponent(EduCourseUpdater.class); - } - - public EduCourseUpdater() { - ApplicationManager.getApplication().executeOnPooledThread( - () -> ourRemoteCourses = new StudyProjectGenerator().getCourses(true)); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduNextRecommendationCheckListener.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduNextRecommendationCheckListener.java deleted file mode 100644 index 0e0034e651df..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduNextRecommendationCheckListener.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.intellij.openapi.progress.PerformInBackgroundOption; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.checker.StudyCheckListener; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; - -public class EduNextRecommendationCheckListener implements StudyCheckListener { - - private StudyStatus myStatusBeforeCheck; - - @Override - public void beforeCheck(@NotNull Project project, @NotNull Task task) { - myStatusBeforeCheck = task.getStatus(); - } - - @Override - public void afterCheck(@NotNull Project project, @NotNull Task task) { - Course course = task.getLesson().getCourse(); - if (!(course instanceof RemoteCourse && course.isAdaptive())) { - return; - } - if (myStatusBeforeCheck == StudyStatus.Solved) { - return; - } - StudyStatus statusAfterCheck = task.getStatus(); - if (statusAfterCheck != StudyStatus.Solved) { - return; - } - ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Backgroundable(project, EduAdaptiveStepicConnector.LOADING_NEXT_RECOMMENDATION, false, - PerformInBackgroundOption.DEAF) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - indicator.setIndeterminate(true); - EduAdaptiveStepicConnector.addNextRecommendedTask(project, task.getLesson(), indicator, EduAdaptiveStepicConnector.NEXT_RECOMMENDATION_REACTION); - } - }); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicAuthorizedClient.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicAuthorizedClient.java deleted file mode 100644 index 7fff5a901c67..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicAuthorizedClient.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.intellij.openapi.diagnostic.Logger; -import com.jetbrains.edu.learning.StudySettings; -import org.apache.http.*; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.message.BasicHeader; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import static com.jetbrains.edu.learning.stepic.EduStepicClient.getBuilder; - -public class EduStepicAuthorizedClient { - private static final Logger LOG = Logger.getInstance(EduStepicAuthorizedClient.class.getName()); - - private static CloseableHttpClient ourClient; - - private EduStepicAuthorizedClient() { - } - - @Nullable - public static CloseableHttpClient getHttpClient() { - if (ourClient != null) { - return ourClient; - } - - StudySettings studySettings = StudySettings.getInstance(); - - assert studySettings.getUser() != null: "User must not be null"; - - StepicUser stepicUser = studySettings.getUser(); - assert stepicUser != null; - - if (!EduStepicClient.isTokenUpToDate(stepicUser.getAccessToken())) { - StepicWrappers.TokenInfo tokens = getUpdatedTokens(stepicUser.getRefreshToken()); - if (tokens != null) { - stepicUser.setTokenInfo(tokens); - } - else { - return null; - } - } - - ourClient = createInitializedClient(stepicUser.getAccessToken()); - - return ourClient; - } - - @Nullable - public static T getFromStepic(@NotNull String link, @NotNull final Class container) throws IOException { - final CloseableHttpClient client = getHttpClient(); - return client == null ? null : EduStepicClient.getFromStepic(link, container, client); - } - - /* - * This method should be used only in project generation while project is not available. - * Make sure you saved stepic user in task manager after using this method. - */ - @NotNull - public static CloseableHttpClient getHttpClient(@NotNull final StepicUser stepicUser) { - if (ourClient != null) { - return ourClient; - } - - if (!EduStepicClient.isTokenUpToDate(stepicUser.getAccessToken())) { - StepicWrappers.TokenInfo tokenInfo = getUpdatedTokens(stepicUser.getRefreshToken()); - if (tokenInfo != null) { - stepicUser.setTokenInfo(tokenInfo); - } - else { - return EduStepicClient.getHttpClient(); - } - } - - ourClient = createInitializedClient(stepicUser.getAccessToken()); - - return ourClient; - } - - /* - * This method should be used only in project generation while project is not available. - * Make sure you saved stepic user in task manager after using this method. - */ - public static T getFromStepic(String link, final Class container, @NotNull final StepicUser stepicUser) throws IOException { - return EduStepicClient.getFromStepic(link, container, getHttpClient(stepicUser)); - } - - @NotNull - private static CloseableHttpClient createInitializedClient(@NotNull String accessToken) { - final List headers = new ArrayList<>(); - headers.add(new BasicHeader("Authorization", "Bearer " + accessToken)); - headers.add(new BasicHeader("Content-type", EduStepicNames.CONTENT_TYPE_APP_JSON)); - return getBuilder().setDefaultHeaders(headers).build(); - } - - @Nullable - public static StepicUser login(@NotNull final String code, String redirectUrl) { - final List parameters = new ArrayList<>(); - parameters.add(new BasicNameValuePair("grant_type", "authorization_code")); - parameters.add(new BasicNameValuePair("code", code)); - parameters.add(new BasicNameValuePair("redirect_uri", redirectUrl)); - parameters.add(new BasicNameValuePair("client_id", EduStepicNames.CLIENT_ID)); - - StepicWrappers.TokenInfo tokenInfo = getTokens(parameters); - if (tokenInfo != null) { - final StepicUser user = new StepicUser(tokenInfo); - ourClient = createInitializedClient(user.getAccessToken()); - - final StepicUser currentUser = getCurrentUser(); - if (currentUser != null) { - user.setId(currentUser.getId()); - user.setFirstName(currentUser.getFirstName()); - user.setLastName(currentUser.getLastName()); - } - return user; - } - - return null; - } - - public static void invalidateClient() { - ourClient = null; - } - - @Nullable - private static StepicWrappers.TokenInfo getUpdatedTokens(@NotNull final String refreshToken) { - final List parameters = new ArrayList<>(); - parameters.add(new BasicNameValuePair("client_id", EduStepicNames.CLIENT_ID)); - parameters.add(new BasicNameValuePair("content-type", "application/json")); - parameters.add(new BasicNameValuePair("grant_type", "refresh_token")); - parameters.add(new BasicNameValuePair("refresh_token", refreshToken)); - - return getTokens(parameters); - } - - @Nullable - public static StepicUser getCurrentUser() { - CloseableHttpClient client = getHttpClient(); - if (client != null) { - try { - final StepicWrappers.AuthorWrapper wrapper = EduStepicClient.getFromStepic(EduStepicNames.CURRENT_USER, - StepicWrappers.AuthorWrapper.class, - client); - if (wrapper != null && !wrapper.users.isEmpty()) { - return wrapper.users.get(0); - } - } - catch (IOException e) { - LOG.warn("Couldn't get a current user"); - } - } - return null; - } - - @Nullable - private static StepicWrappers.TokenInfo getTokens(@NotNull final List parameters) { - final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - - final HttpPost request = new HttpPost(EduStepicNames.TOKEN_URL); - request.setEntity(new UrlEncodedFormEntity(parameters, Consts.UTF_8)); - - try { - final CloseableHttpClient client = EduStepicClient.getHttpClient(); - final CloseableHttpResponse response = client.execute(request); - final StatusLine statusLine = response.getStatusLine(); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - EntityUtils.consume(responseEntity); - if (statusLine.getStatusCode() == HttpStatus.SC_OK) { - return gson.fromJson(responseString, StepicWrappers.TokenInfo.class); - } - else { - LOG.warn("Failed to get tokens: " + statusLine.getStatusCode() + statusLine.getReasonPhrase()); - } - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - return null; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicClient.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicClient.java deleted file mode 100644 index e1b6bfee8f9a..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicClient.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.util.net.HttpConfigurable; -import com.intellij.util.net.ssl.CertificateManager; -import com.intellij.util.net.ssl.ConfirmingTrustManager; -import com.jetbrains.edu.learning.StudySerializationUtils; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHost; -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.impl.DefaultConnectionReuseStrategy; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.message.BasicHeader; -import org.apache.http.util.EntityUtils; -import org.jetbrains.annotations.NotNull; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.URI; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.List; - -public class EduStepicClient { - private static final Logger LOG = Logger.getInstance(EduStepicClient.class.getName()); - private static CloseableHttpClient ourClient; - - private EduStepicClient() { - } - - @NotNull - public static CloseableHttpClient getHttpClient() { - if (ourClient == null) { - initializeClient(); - } - return ourClient; - } - - public static T getFromStepic(String link, final Class container) throws IOException { - return getFromStepic(link, container, getHttpClient()); - } - - static T getFromStepic(String link, final Class container, @NotNull final CloseableHttpClient client) throws IOException { - if (!link.startsWith("/")) link = "/" + link; - final HttpGet request = new HttpGet(EduStepicNames.STEPIC_API_URL + link); - - final CloseableHttpResponse response = client.execute(request); - final StatusLine statusLine = response.getStatusLine(); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - EntityUtils.consume(responseEntity); - if (statusLine.getStatusCode() != HttpStatus.SC_OK) { - throw new IOException("Stepic returned non 200 status code " + responseString); - } - return deserializeStepicResponse(container, responseString); - } - - static T deserializeStepicResponse(Class container, String responseString) { - Gson gson = - new GsonBuilder().registerTypeAdapter(StepicWrappers.StepOptions.class, new StudySerializationUtils.Json.StepicStepOptionsAdapter()). - setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"). - setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - return gson.fromJson(responseString, container); - } - - private static void initializeClient() { - if (ourClient == null) { - final HttpClientBuilder builder = getBuilder(); - ourClient = builder.build(); - } - } - - @NotNull - static HttpClientBuilder getBuilder() { - final HttpClientBuilder builder = HttpClients.custom().setSSLContext(CertificateManager.getInstance().getSslContext()). - setMaxConnPerRoute(100000).setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE); - - final HttpConfigurable proxyConfigurable = HttpConfigurable.getInstance(); - final List proxies = proxyConfigurable.getOnlyBySettingsSelector().select(URI.create(EduStepicNames.STEPIC_URL)); - final InetSocketAddress address = proxies.size() > 0 ? (InetSocketAddress)proxies.get(0).address() : null; - if (address != null) { - builder.setProxy(new HttpHost(address.getHostName(), address.getPort())); - } - final ConfirmingTrustManager trustManager = CertificateManager.getInstance().getTrustManager(); - try { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom()); - builder.setSSLContext(sslContext); - } - catch (NoSuchAlgorithmException | KeyManagementException e) { - LOG.error(e.getMessage()); - } - return builder; - } - - static boolean isTokenUpToDate(@NotNull String token) { - if (token.isEmpty()) return false; - - final List headers = new ArrayList<>(); - headers.add(new BasicHeader("Authorization", "Bearer " + token)); - headers.add(new BasicHeader("Content-type", EduStepicNames.CONTENT_TYPE_APP_JSON)); - CloseableHttpClient httpClient = getBuilder().setDefaultHeaders(headers).build(); - - try { - final StepicWrappers.AuthorWrapper wrapper = - getFromStepic(EduStepicNames.CURRENT_USER, StepicWrappers.AuthorWrapper.class, httpClient); - if (wrapper != null && !wrapper.users.isEmpty()) { - StepicUser user = wrapper.users.get(0); - return user != null && !user.isGuest(); - } - else { - throw new IOException(wrapper == null ? "Got a null current user" : "Got an empty wrapper"); - } - } - catch (IOException e) { - LOG.warn(e.getMessage()); - return false; - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java deleted file mode 100644 index dd9bddc0e60d..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ /dev/null @@ -1,522 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.intellij.ide.BrowserUtil; -import com.intellij.lang.LanguageExtensionPoint; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudySettings; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.*; -import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import org.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.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 org.jetbrains.builtInWebServer.BuiltInServerOptions; -import org.jetbrains.ide.BuiltInServerManager; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.*; - -import static com.jetbrains.edu.learning.stepic.EduStepicNames.PYCHARM_PREFIX; - -public class EduStepicConnector { - private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName()); - - public static final int CURRENT_VERSION = 2; - //this prefix indicates that course can be opened by educational plugin - private static final String ADAPTIVE_NOTE = - "\n\nInitially, the adaptive system may behave somewhat randomly, but the more problems you solve, the smarter it becomes!"; - - private EduStepicConnector() { - } - - public static boolean enrollToCourse(final int courseId, @Nullable final StepicUser stepicUser) { - if (stepicUser == null) return false; - HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.ENROLLMENTS); - try { - final StepicWrappers.EnrollmentWrapper enrollment = new StepicWrappers.EnrollmentWrapper(String.valueOf(courseId)); - post.setEntity(new StringEntity(new GsonBuilder().create().toJson(enrollment))); - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(stepicUser); - CloseableHttpResponse response = client.execute(post); - StatusLine line = response.getStatusLine(); - return line.getStatusCode() == HttpStatus.SC_CREATED; - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - return false; - } - - @NotNull - public static List getCourses(@Nullable StepicUser user) { - try { - List result = new ArrayList<>(); - int pageNumber = 1; - while (addCoursesFromStepic(user, result, pageNumber)) { - pageNumber += 1; - } - return result; - } - catch (IOException e) { - LOG.warn("Cannot load course list " + e.getMessage()); - } - return Collections.emptyList(); - } - - public static Date getCourseUpdateDate(final int courseId) { - final String url = EduStepicNames.COURSES + "/" + courseId; - try { - final List courses = EduStepicClient.getFromStepic(url, StepicWrappers.CoursesContainer.class).courses; - if (!courses.isEmpty()) { - return courses.get(0).getUpdateDate(); - } - } - catch (IOException e) { - LOG.warn("Could not retrieve course with id=" + courseId); - } - - return null; - } - - public static Date getLessonUpdateDate(final int lessonId) { - final String url = EduStepicNames.LESSONS + "/" + lessonId; - try { - List lessons = EduStepicClient.getFromStepic(url, StepicWrappers.LessonContainer.class).lessons; - if (!lessons.isEmpty()) { - return lessons.get(0).getUpdateDate(); - } - } - catch (IOException e) { - LOG.warn("Could not retrieve lesson with id=" + lessonId); - } - - return null; - } - - public static Date getTaskUpdateDate(final int taskId) { - final String url = EduStepicNames.STEPS + String.valueOf(taskId); - try { - List steps = EduStepicClient.getFromStepic(url, StepicWrappers.StepContainer.class).steps; - if (!steps.isEmpty()) { - return steps.get(0).update_date; - } - } - catch (IOException e) { - LOG.warn("Could not retrieve task with id=" + taskId); - } - - return null; - } - - public static StepicWrappers.CoursesContainer getCoursesFromStepik(@Nullable StepicUser user, URI url) throws IOException { - final StepicWrappers.CoursesContainer coursesContainer; - if (user != null) { - coursesContainer = EduStepicAuthorizedClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class, user); - } - else { - coursesContainer = EduStepicClient.getFromStepic(url.toString(), StepicWrappers.CoursesContainer.class); - } - return coursesContainer; - } - - private static boolean addCoursesFromStepic(@Nullable StepicUser user, List result, int pageNumber) throws IOException { - final URI url; - try { - url = new URIBuilder(EduStepicNames.COURSES).addParameter("is_idea_compatible", "true"). - addParameter("page", String.valueOf(pageNumber)).build(); - } - catch (URISyntaxException e) { - LOG.error(e.getMessage()); - return false; - } - final StepicWrappers.CoursesContainer coursesContainer = getCoursesFromStepik(user, url); - addAvailableCourses(result, coursesContainer); - return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE; - } - - @Nullable - public static Course getCourseFromStepik(@Nullable StepicUser user, int courseId) throws IOException { - final URI url; - try { - url = new URIBuilder(EduStepicNames.COURSES + "/" + courseId).addParameter("is_idea_compatible", "true") - .build(); - } - catch (URISyntaxException e) { - LOG.error(e.getMessage()); - return null; - } - final StepicWrappers.CoursesContainer coursesContainer = getCoursesFromStepik(user, url); - - if (coursesContainer != null && !coursesContainer.courses.isEmpty()) { - return coursesContainer.courses.get(0); - } else { - return null; - } - } - - static void addAvailableCourses(List result, StepicWrappers.CoursesContainer coursesContainer) throws IOException { - final List courses = coursesContainer.courses; - for (RemoteCourse info : courses) { - if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(info.getType())) continue; - setCourseLanguage(info); - - if (canBeOpened(info)) { - final ArrayList authors = new ArrayList<>(); - for (Integer instructor : info.getInstructors()) { - final StepicUser author = EduStepicClient.getFromStepic(EduStepicNames.USERS + String.valueOf(instructor), - StepicWrappers.AuthorWrapper.class).users.get(0); - authors.add(author); - } - info.setAuthors(authors); - - if (info.isAdaptive()) { - info.setDescription("This is a Stepik Adaptive course.\n\n" + info.getDescription() + ADAPTIVE_NOTE); - } - result.add(info); - } - } - } - - private static void setCourseLanguage(RemoteCourse info) { - String courseType = info.getType(); - final int separator = courseType.indexOf(" "); - assert separator != -1; - final String language = courseType.substring(separator + 1); - info.setLanguage(language); - } - - static boolean canBeOpened(RemoteCourse courseInfo) { - final ArrayList supportedLanguages = new ArrayList<>(); - final LanguageExtensionPoint[] extensions = Extensions.getExtensions(EduPluginConfigurator.EP_NAME, null); - for (LanguageExtensionPoint extension : extensions) { - String languageId = extension.getKey(); - supportedLanguages.add(languageId); - } - - if (courseInfo.isAdaptive()) { - return supportedLanguages.contains(courseInfo.getLanguageID()); - } - - String courseType = courseInfo.getType(); - final List typeLanguage = StringUtil.split(courseType, " "); - String prefix = typeLanguage.get(0); - if (!supportedLanguages.contains(courseInfo.getLanguageID())) return false; - if (typeLanguage.size() < 2 || !prefix.startsWith(PYCHARM_PREFIX)) { - return false; - } - String versionString = prefix.substring(PYCHARM_PREFIX.length()); - if (versionString.isEmpty()) { - return true; - } - try { - Integer version = Integer.valueOf(versionString); - return version <= CURRENT_VERSION; - } - catch (NumberFormatException e) { - LOG.info("Wrong version format", e); - return false; - } - } - - public static RemoteCourse getCourse(@NotNull final Project project, @NotNull final RemoteCourse remoteCourse) { - final List lessons = remoteCourse.getLessons(true); - if (!lessons.isEmpty()) return remoteCourse; - if (!remoteCourse.isAdaptive()) { - try { - for (Integer section : remoteCourse.getSections()) { - remoteCourse.addLessons(getLessons(section)); - } - return remoteCourse; - } - catch (IOException e) { - LOG.error("IOException " + e.getMessage()); - } - } - else { - final Lesson lesson = new Lesson(); - lesson.setName(EduNames.ADAPTIVE); - remoteCourse.addLesson(lesson); - //TODO: more specific name? - final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, remoteCourse); - if (recommendation != null) { - lesson.addTask(recommendation); - } - return remoteCourse; - } - return null; - } - - public static List getLessons(int sectionId) throws IOException { - final StepicWrappers.SectionContainer - sectionContainer = getFromStepik(EduStepicNames.SECTIONS + String.valueOf(sectionId), - StepicWrappers.SectionContainer.class); - List unitIds = sectionContainer.sections.get(0).units; - final List lessons = new ArrayList<>(); - for (Integer unitId : unitIds) { - StepicWrappers.UnitContainer - unit = getFromStepik(EduStepicNames.UNITS + "/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class); - int lessonID = unit.units.get(0).lesson; - StepicWrappers.LessonContainer - lessonContainer = getFromStepik(EduStepicNames.LESSONS + String.valueOf(lessonID), - StepicWrappers.LessonContainer.class); - Lesson lesson = lessonContainer.lessons.get(0); - lesson.taskList = new ArrayList<>(); - for (int stepId : lesson.steps) { - final Task task = createTask(stepId); - if (task != null) { - lesson.addTask(task); - } - } - if (!lesson.taskList.isEmpty()) { - lessons.add(lesson); - } - } - - return lessons; - } - - private static T getFromStepik(String link, final Class container) throws IOException { - final StepicUser user = StudySettings.getInstance().getUser(); - final boolean isAuthorized = user != null; - if (isAuthorized) { - return EduStepicAuthorizedClient.getFromStepic(link, container, user); - } - return EduStepicClient.getFromStepic(link, container); - } - - @Nullable - public static Task createTask(int stepicId) throws IOException { - final StepicWrappers.StepSource step = getStep(stepicId); - final StepicWrappers.Step block = step.block; - if (!block.name.startsWith(PYCHARM_PREFIX)) { - LOG.error("Got a block with non-pycharm prefix: " + block.name + " for step: " + stepicId); - return null; - } - final int lastSubtaskIndex = block.options.lastSubtaskIndex; - Task task = new PyCharmTask(); - if (lastSubtaskIndex != 0) { - task = createTaskWithSubtasks(lastSubtaskIndex); - } - task.setStepId(stepicId); - task.setUpdateDate(step.update_date); - task.setName(block.options != null ? block.options.title : (PYCHARM_PREFIX + CURRENT_VERSION)); - - for (StepicWrappers.FileWrapper wrapper : block.options.test) { - task.addTestsTexts(wrapper.name, wrapper.text); - } - if (block.options.text != null) { - for (StepicWrappers.FileWrapper wrapper : block.options.text) { - task.addTaskText(wrapper.name, wrapper.text); - } - } else { - task.addTaskText(EduNames.TASK, block.text); - } - - task.taskFiles = new HashMap<>(); // TODO: it looks like we don't need taskFiles as map anymore - if (block.options.files != null) { - for (TaskFile taskFile : block.options.files) { - addPlaceholdersTexts(taskFile); - task.taskFiles.put(taskFile.name, taskFile); - } - } - return task; - } - - private static void addPlaceholdersTexts(TaskFile file) { - final String fileText = file.text; - final List placeholders = file.getAnswerPlaceholders(); - for (AnswerPlaceholder placeholder : placeholders) { - final AnswerPlaceholderSubtaskInfo info = placeholder.getActiveSubtaskInfo(); - if (info == null) { - continue; - } - final int offset = placeholder.getOffset(); - final int length = placeholder.getLength(); - if (fileText.length() > offset + length) { - info.setPlaceholderText(fileText.substring(offset, offset + length)); - } - } - } - - @NotNull - private static Task createTaskWithSubtasks(int lastSubtaskIndex) { - TaskWithSubtasks task = new TaskWithSubtasks(); - task.setLastSubtaskIndex(lastSubtaskIndex); - return task; - } - - public static StepicWrappers.StepSource getStep(int step) throws IOException { - return getFromStepik(EduStepicNames.STEPS + String.valueOf(step), - StepicWrappers.StepContainer.class).steps.get(0); - } - - public static void postSolution(@NotNull final Task task, boolean passed, @NotNull final Project project) { - if (task.getStepId() <= 0) { - return; - } - - try { - final String response = postAttempt(task.getStepId()); - if (response.isEmpty()) return; - final StepicWrappers.AttemptWrapper.Attempt attempt = - new Gson().fromJson(response, StepicWrappers.AttemptContainer.class).attempts.get(0); - final Map taskFiles = task.getTaskFiles(); - final ArrayList files = new ArrayList<>(); - final VirtualFile taskDir = task.getTaskDir(project); - if (taskDir == null) { - LOG.error("Failed to find task directory " + task.getName()); - return; - } - for (TaskFile fileEntry : taskFiles.values()) { - final String fileName = fileEntry.name; - final VirtualFile virtualFile = taskDir.findFileByRelativePath(fileName); - if (virtualFile != null) { - ApplicationManager.getApplication().runReadAction(() -> { - final Document document = FileDocumentManager.getInstance().getDocument(virtualFile); - if (document != null) { - files.add(new StepicWrappers.SolutionFile(fileName, document.getCharsSequence().toString())); - } - }); - } - } - - postSubmission(passed, attempt, files); - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - } - - public static String postAttempt(int id) throws IOException { - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null || StudySettings.getInstance().getUser() == null) return ""; - final HttpPost attemptRequest = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.ATTEMPTS); - String attemptRequestBody = new Gson().toJson(new StepicWrappers.AttemptWrapper(id)); - attemptRequest.setEntity(new StringEntity(attemptRequestBody, ContentType.APPLICATION_JSON)); - - final CloseableHttpResponse attemptResponse = client.execute(attemptRequest); - final HttpEntity responseEntity = attemptResponse.getEntity(); - final String attemptResponseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - final StatusLine statusLine = attemptResponse.getStatusLine(); - EntityUtils.consume(responseEntity); - if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) { - LOG.warn("Failed to make attempt " + attemptResponseString); - return ""; - } - return attemptResponseString; - } - - private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt, - ArrayList files) throws IOException { - final HttpPost request = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.SUBMISSIONS); - - String requestBody = new Gson().toJson(new StepicWrappers.SubmissionWrapper(attempt.id, passed ? "1" : "0", files)); - request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); - final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); - if (client == null) return; - final CloseableHttpResponse response = client.execute(request); - final HttpEntity responseEntity = response.getEntity(); - final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - final StatusLine line = response.getStatusLine(); - EntityUtils.consume(responseEntity); - if (line.getStatusCode() != HttpStatus.SC_CREATED) { - LOG.error("Failed to make submission " + responseString); - } - } - - @NotNull - public static String createOAuthLink(String authRedirectUrl) { - return "https://stepik.org/oauth2/authorize/" + - "?client_id=" + EduStepicNames.CLIENT_ID + - "&redirect_uri=" + authRedirectUrl + - "&response_type=code"; - } - - @NotNull - public static String getOAuthRedirectUrl() { - int port = BuiltInServerManager.getInstance().getPort(); - - // according to https://confluence.jetbrains.com/display/IDEADEV/Remote+communication - int defaultPort = BuiltInServerOptions.getInstance().builtInServerPort; - if (port >= defaultPort && port < (defaultPort + 20)) { - return "http://localhost:" + port + "/api/" + EduStepicNames.OAUTH_SERVICE_NAME; - } - - return EduStepicNames.EXTERNAL_REDIRECT_URL; - } - - public static void doAuthorize(@NotNull Runnable externalRedirectUrlHandler) { - EduUsagesCollector.stepikAuthorization(); - String redirectUrl = getOAuthRedirectUrl(); - String link = createOAuthLink(redirectUrl); - BrowserUtil.browse(link); - if (!redirectUrl.startsWith("http://localhost")) { - externalRedirectUrlHandler.run(); - } - } - - public static StepicWrappers.Unit getUnit(int unitId) { - try { - List units = - getFromStepik(EduStepicNames.UNITS + "/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class).units; - if (!units.isEmpty()) { - return units.get(0); - } - } - catch (IOException e) { - LOG.warn("Failed getting unit: " + unitId); - } - return new StepicWrappers.Unit(); - } - - public static StepicWrappers.Section getSection(int sectionId) { - try { - List sections = - getFromStepik(EduStepicNames.SECTIONS + "/" + String.valueOf(sectionId), StepicWrappers.SectionContainer.class).getSections(); - if (!sections.isEmpty()) { - return sections.get(0); - } - } - catch (IOException e) { - LOG.warn("Failed getting section: " + sectionId); - } - return new StepicWrappers.Section(); - } - - public static Lesson getLesson(int lessonId) { - try { - List lessons = - getFromStepik(EduStepicNames.LESSONS + "/" + String.valueOf(lessonId), StepicWrappers.LessonContainer.class).lessons; - if (!lessons.isEmpty()) { - return lessons.get(0); - } - } - catch (IOException e) { - LOG.warn("Failed getting section: " + lessonId); - } - return new Lesson(); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java deleted file mode 100644 index 525b59aa1dfe..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicNames.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -public class EduStepicNames { - public static final String STEPIC_URL = "https://stepik.org"; - public static final String TOKEN_URL = STEPIC_URL + "/oauth2/token/"; - public static final String STEPIC_API_URL = STEPIC_URL + "/api"; - - public static final String RECOMMENDATIONS_URL = "/recommendations"; - public static final String CONTENT_TYPE_APP_JSON = "application/json"; - public static final String LESSONS = "/lessons/"; - public static final String RECOMMENDATION_REACTIONS_URL = "/recommendation-reactions"; - public static final String ATTEMPTS = "/attempts"; - public static final String SUBMISSIONS = "/submissions"; - public static final String UNITS = "/units"; - public static final String USERS = "/users/"; - public static final String STEP_SOURCES = "/step-sources/"; - public static final String CURRENT_USER = "/stepics/1"; - public static final String COURSES = "/courses"; - public static final String STEPS = "/steps/"; - public static final String SECTIONS = "/sections/"; - public static final String ENROLLMENTS = "/enrollments"; - public static final String PYCHARM_PREFIX = "pycharm"; - public static final String EDU_STEPIK_SERVICE_NAME = "edu/stepik"; - public static final String STEP_ID = "step_id"; - public static final String LINK = "link"; - public static final String CLIENT_ID = "Xqr8Ez716cUJHezxCDnK8bKfMAMqpBYxWQmUxZ8D"; - public static final String OAUTH_SERVICE_NAME = "edu/stepik/oauth"; - public static final String EXTERNAL_REDIRECT_URL = "https://example.com"; -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicUpdater.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicUpdater.java deleted file mode 100644 index ac5c4b1a438a..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepicUpdater.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.intellij.ide.AppLifecycleListener; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationType; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.util.ActionCallback; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.Alarm; -import com.intellij.util.text.DateFormatUtil; -import com.jetbrains.edu.learning.StudySettings; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class EduStepicUpdater { - private static final long CHECK_INTERVAL = DateFormatUtil.DAY; - - private final Runnable myCheckRunnable = () -> updateCourseList().doWhenDone(() -> queueNextCheck(CHECK_INTERVAL)); - private final Alarm myCheckForUpdatesAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); - - public EduStepicUpdater(@NotNull Application application) { - scheduleCourseListUpdate(application); - } - - public void scheduleCourseListUpdate(Application application) { - if (!checkNeeded()) { - return; - } - application.getMessageBus().connect(application).subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener() { - @Override - public void appFrameCreated(String[] commandLineArgs, @NotNull Ref willOpenProject) { - - long timeToNextCheck = StudySettings.getInstance().getLastTimeChecked() + CHECK_INTERVAL - System.currentTimeMillis(); - if (timeToNextCheck <= 0) { - myCheckRunnable.run(); - } - else { - queueNextCheck(timeToNextCheck); - } - } - }); - } - - private static ActionCallback updateCourseList() { - ActionCallback callback = new ActionCallback(); - ApplicationManager.getApplication().executeOnPooledThread(() -> { - final List courses = EduStepicConnector.getCourses(null); - StudySettings.getInstance().setLastTimeChecked(System.currentTimeMillis()); - - if (!courses.isEmpty()) { - List updated = new ArrayList<>(); - for (Course course : courses) { - if (course instanceof RemoteCourse && ((RemoteCourse)course).getUpdateDate(). - after(new Date(StudySettings.getInstance().getLastTimeChecked()))) { - updated.add(course); - } - } - if (updated.isEmpty()) return; - final String message; - final String title; - if (updated.size() == 1) { - message = updated.get(0).getName(); - title = "New course available"; - } - else { - title = "New courses available"; - message = StringUtil.join(updated, Course::getName, ", "); - } - final Notification notification = new Notification("New.course", title, message, NotificationType.INFORMATION); - notification.notify(null); - } - }); - return callback; - } - - private void queueNextCheck(long interval) { - myCheckForUpdatesAlarm.addRequest(myCheckRunnable, interval); - } - - private static boolean checkNeeded() { - long timeToNextCheck = StudySettings.getInstance().getLastTimeChecked() + CHECK_INTERVAL - System.currentTimeMillis(); - return timeToNextCheck <= 0; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepikUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepikUtils.java deleted file mode 100644 index 00516167744e..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduStepikUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2017 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.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.Nullable; - -public class EduStepikUtils { - @Nullable - public static String getLink(@Nullable Task task, int stepNumber) { - if (task == null) { - return null; - } - Lesson lesson = task.getLesson(); - if (lesson == null || !(lesson.getCourse() instanceof RemoteCourse)) { - return null; - } - - return String.format("%s/lesson/%d/step/%d", EduStepicNames.STEPIC_URL, lesson.getId(), stepNumber); - } - - @Nullable - public static String getAdaptiveLink(@Nullable Task task) { - String link = getLink(task, 1); - return link == null ? null : link + "?adaptive=true"; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/OAuthDialog.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/OAuthDialog.java deleted file mode 100644 index f74e3fd4fa36..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/OAuthDialog.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.DefaultProjectFactoryImpl; -import com.intellij.openapi.ui.DialogWrapper; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.ui.AuthorizationPanel; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; - -public class OAuthDialog extends DialogWrapper { - protected final AuthorizationPanel myLoginPanel; - private String myProgressTitle; - private StepicUser myStepicUser; - - public OAuthDialog() { - super(false); - myLoginPanel = new AuthorizationPanel(); - myProgressTitle = "Authorizing on Stepik"; - setTitle("Stepik Authorization"); - init(); - } - - @NotNull - protected Action[] createActions() { - return new Action[]{getOKAction(), getCancelAction()}; - } - - @Override - protected JComponent createCenterPanel() { - return myLoginPanel.getContentPanel(); - } - - @Override - protected String getHelpId() { - return "login_to_stepic"; - } - - @Override - public JComponent getPreferredFocusedComponent() { - return myLoginPanel.getPreferableFocusComponent(); - } - - @Override - protected void doOKAction() { - String code = myLoginPanel.getCode(); - if (code == null || code.isEmpty()) return; - - ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { - ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - - myStepicUser = StudyUtils.execCancelable(() -> EduStepicAuthorizedClient.login(code, EduStepicNames.EXTERNAL_REDIRECT_URL)); - if (myStepicUser != null) { - doJustOkAction(); - } - else { - setError("Login Failed"); - } - }, myProgressTitle, true, new DefaultProjectFactoryImpl().getDefaultProject()); - } - - private void setError(@NotNull String errorText) { - ApplicationManager.getApplication().invokeLater(() -> setErrorText(errorText)); - } - - protected void doJustOkAction() { - ApplicationManager.getApplication().invokeLater(() -> super.doOKAction()); - } - - public StepicUser getStepicUser() { - return myStepicUser; - } -} \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java deleted file mode 100644 index a8d6d0fc418c..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.intellij.openapi.application.ApplicationManager; -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.ui.JBColor; -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; - -import static com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector.TOO_BORING_RECOMMENDATION_REACTION; -import static com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector.TOO_HARD_RECOMMENDATION_REACTION; - - -public class StepicAdaptiveReactionsPanel extends JPanel { - private final ReactionButtonPanel myHardPanel; - private final ReactionButtonPanel myBoringPanel; - @NotNull private final Project myProject; - private static final String HARD_REACTION = "Too Hard"; - private static final String BORING_REACTION = "Too Boring"; - private static final String SOLVED_TASK_TOOLTIP = "Reaction Disabled Due To 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, TOO_HARD_RECOMMENDATION_REACTION); - myBoringPanel = new ReactionButtonPanel(BORING_REACTION, BORING_LABEL_TOOLTIP, TOO_BORING_RECOMMENDATION_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 selectionChanged(@NotNull FileEditorManagerEvent event) { - final com.jetbrains.edu.learning.courseFormat.tasks.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, - int reaction) { - com.jetbrains.edu.learning.courseFormat.tasks.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 && task.getStatus() == StudyStatus.Solved ? enabledTooltip : SOLVED_TASK_TOOLTIP); - myButtonPanel.add(Box.createVerticalStrut(5)); - myButtonPanel.add(myLabel); - myButtonPanel.add(Box.createVerticalStrut(5)); - - setEnabledRecursive(isEnabled); - - setLayout(new GridBagLayout()); - setBorder(BorderFactory.createEtchedBorder()); - add(myButtonPanel); - addMouseListener(reaction); - } - - private void addMouseListener(int reaction) { - final ReactionMouseAdapter mouseAdapter = new ReactionMouseAdapter(this, reaction); - 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 ReactionButtonPanel myPanel; - private final int myReaction; - - public ReactionMouseAdapter(@NotNull final ReactionButtonPanel mainPanel, int reaction) { - myReaction = reaction; - myPanel = mainPanel; - } - - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 1 && isEnabled()) { - final com.jetbrains.edu.learning.courseFormat.tasks.Task task = StudyUtils.getCurrentTask(myProject); - if (task != null && task.getStatus() != StudyStatus.Solved) { - final ProgressIndicatorBase progress = new ProgressIndicatorBase(); - progress.setText(EduAdaptiveStepicConnector.LOADING_NEXT_RECOMMENDATION); - ProgressManager.getInstance().run(new Task.Backgroundable(myProject, - EduAdaptiveStepicConnector.LOADING_NEXT_RECOMMENDATION) { - @Override - public void run(@NotNull ProgressIndicator indicator) { - StepicAdaptiveReactionsPanel.this.setEnabledRecursive(false); - ApplicationManager.getApplication().invokeLater(()->setBackground(UIUtil.getLabelBackground())); - EduAdaptiveStepicConnector.addNextRecommendedTask(StepicAdaptiveReactionsPanel.this.myProject, task.getLesson(), indicator, - myReaction); - StepicAdaptiveReactionsPanel.this.setEnabledRecursive(true); - } - }); - } - } - } - - @Override - public void mouseEntered(MouseEvent e) { - final com.jetbrains.edu.learning.courseFormat.tasks.Task task = StudyUtils.getCurrentTask(myProject); - if (task != null && task.getStatus() != StudyStatus.Solved && myPanel.isEnabled()) { - setBackground(JBColor.GRAY); - } - } - - @Override - public void mouseExited(MouseEvent e) { - setBackground(UIUtil.getLabelBackground()); - } - - private void setBackground(Color color) { - myPanel.setBackground(color); - myButtonPanel.setBackground(color); - } - } - } -} - diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.form b/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.form deleted file mode 100644 index 5b1d9c4ffee4..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.form +++ /dev/null @@ -1,67 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.java deleted file mode 100644 index 2fb8dbc82f3a..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.java +++ /dev/null @@ -1,172 +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.openapi.application.ApplicationManager; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.ui.HoverHyperlinkLabel; -import com.intellij.ui.HyperlinkAdapter; -import com.intellij.ui.components.JBCheckBox; -import com.intellij.ui.components.JBLabel; -import com.jetbrains.edu.learning.StudySettings; -import com.jetbrains.edu.learning.settings.StudyOptionsProvider; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; - -public class StepicStudyOptions implements StudyOptionsProvider { - private JPanel myPane; - private JBCheckBox myEnableTestingFromSamples; - private JBLabel myUsernameLabel; - private HoverHyperlinkLabel myHoverHyperlinkLabel; - private StepicUser myStepicUser; - private HyperlinkAdapter myListener; - - public StepicStudyOptions() { - } - - @NotNull - private HyperlinkAdapter createLogoutListener() { - return new HyperlinkAdapter() { - @Override - protected void hyperlinkActivated(HyperlinkEvent e) { - removeCredentials(); - updateLoginLabels(null); - } - }; - } - - @NotNull - private HyperlinkAdapter createAuthorizeListener() { - return new HyperlinkAdapter() { - - @Override - protected void hyperlinkActivated(HyperlinkEvent e) { - ApplicationManager.getApplication().getMessageBus().connect().subscribe(StudySettings.SETTINGS_CHANGED, () -> { - StepicUser user = StudySettings.getInstance().getUser(); - if (user != null && !user.equals(myStepicUser)) { - StudySettings.getInstance().setUser(myStepicUser); - myStepicUser = user; - updateLoginLabels(myStepicUser); - } - }); - - EduStepicConnector.doAuthorize(() -> showDialog()); - } - }; - } - - private void showDialog() { - OAuthDialog dialog = new OAuthDialog(); - if (dialog.showAndGet()) { - myStepicUser = dialog.getStepicUser(); - updateLoginLabels(myStepicUser); - } - } - - @NotNull - public JComponent getPanel() { - return myPane; - } - - private boolean isTestingFromSamplesEnabled() { - return myEnableTestingFromSamples.isSelected(); - } - - @Override - public void reset() { - final StudySettings stepikSettings = StudySettings.getInstance(); - myEnableTestingFromSamples.setSelected(stepikSettings.isEnableTestingFromSamples()); - updateLoginLabels(stepikSettings.getUser()); - } - - private void updateLoginLabels(@Nullable StepicUser stepicUser) { - if (myListener != null) { - myHoverHyperlinkLabel.removeHyperlinkListener(myListener); - } - - if (stepicUser == null) { - myUsernameLabel.setText("You're not logged in"); - myHoverHyperlinkLabel.setText("Log in to Stepik"); - - myListener = createAuthorizeListener(); - myHoverHyperlinkLabel.addHyperlinkListener(myListener); - } - else { - String firstName = stepicUser.getFirstName(); - String lastName = stepicUser.getLastName(); - String loggedInText = "You're logged in"; - if (firstName == null || lastName == null || firstName.isEmpty() || lastName.isEmpty()) { - myUsernameLabel.setText(loggedInText); - } - else { - myUsernameLabel.setText(loggedInText + " as " + firstName + " " + lastName); - } - - myHoverHyperlinkLabel.setText("Log out"); - myListener = createLogoutListener(); - myHoverHyperlinkLabel.addHyperlinkListener(myListener); - } - } - - public void createUIComponents() { - myHoverHyperlinkLabel = new HoverHyperlinkLabel(""); - } - - @Override - public void disposeUIResources() { - - } - - @Override - public void apply() throws ConfigurationException { - final StudySettings stepikSettings = StudySettings.getInstance(); - if (isTestingFromSamplesEnabled() != stepikSettings.isEnableTestingFromSamples()) { - stepikSettings.setEnableTestingFromSamples(isTestingFromSamplesEnabled()); - } - - final StepicUser user = stepikSettings.getUser(); - boolean userDeleted = myStepicUser == null && user != null; - boolean userModified = myStepicUser != null && !myStepicUser.equals(user); - if (userDeleted || userModified) { - stepikSettings.setUser(myStepicUser); - } - reset(); - } - - private void removeCredentials() { - myStepicUser = null; - EduStepicAuthorizedClient.invalidateClient(); - } - - @Nullable - @Override - public JComponent createComponent() { - return myPane; - } - - public boolean isModified() { - final StudySettings stepikSettings = StudySettings.getInstance(); - boolean isTestOptionModified = !isTestingFromSamplesEnabled() == stepikSettings.isEnableTestingFromSamples(); - final StepicUser user = stepikSettings.getUser(); - - boolean userDeleted = myStepicUser == null && user != null; - boolean userModified = myStepicUser != null && !myStepicUser.equals(user); - return isTestOptionModified || (userDeleted || userModified); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicUser.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicUser.java deleted file mode 100644 index 53eba8403294..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicUser.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.google.gson.annotations.SerializedName; -import com.intellij.openapi.util.text.StringUtil; -import org.jetbrains.annotations.NotNull; - -public class StepicUser { - private int id = -1; - @SerializedName("first_name") private String myFirstName; - @SerializedName("last_name") private String myLastName; - private String myAccessToken; - private String myRefreshToken; - private boolean isGuest; - - private StepicUser() { - myFirstName = ""; - myLastName = ""; - myAccessToken = ""; - myRefreshToken = ""; - } - - public static StepicUser createEmptyUser() { - return new StepicUser(); - } - - public StepicUser(@NotNull StepicWrappers.TokenInfo tokenInfo) { - setTokenInfo(tokenInfo); - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getFirstName() { - return myFirstName; - } - - public void setFirstName(final String firstName) { - myFirstName = firstName; - } - - public String getLastName() { - return myLastName; - } - - public void setLastName(final String lastName) { - myLastName = lastName; - } - - @NotNull - public String getName() { - return StringUtil.join(new String[]{myFirstName, myLastName}, " "); - } - - @NotNull - public String getAccessToken() { - // for old project where authors were created with null tokens - if (myAccessToken == null) { - return ""; - } - return myAccessToken; - } - - public void setAccessToken(String accessToken) { - this.myAccessToken = accessToken; - } - - @NotNull - public String getRefreshToken() { - // for old project where authors were created with null tokens - if (myRefreshToken == null) { - return ""; - } - return myRefreshToken; - } - - public void setRefreshToken(String refreshToken) { - this.myRefreshToken = refreshToken; - } - - public void setTokenInfo(@NotNull final StepicWrappers.TokenInfo tokenInfo) { - myAccessToken = tokenInfo.getAccessToken(); - myRefreshToken = tokenInfo.getRefreshToken(); - } - - public boolean isGuest() { - return isGuest; - } - - public void setGuest(boolean guest) { - isGuest = guest; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - StepicUser user = (StepicUser)o; - - if (id != user.id) return false; - if (isGuest != user.isGuest) return false; - if (!myFirstName.equals(user.myFirstName)) return false; - if (!myLastName.equals(user.myLastName)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = id; - result = 31 * result + myFirstName.hashCode(); - result = 31 * result + myLastName.hashCode(); - return result; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java deleted file mode 100644 index ea83e46f8d23..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java +++ /dev/null @@ -1,540 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.google.gson.annotations.Expose; -import com.google.gson.annotations.SerializedName; -import com.google.gson.internal.LinkedTreeMap; -import com.intellij.lang.Language; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; - -public class StepicWrappers { - private static final Logger LOG = Logger.getInstance(StepOptions.class); - - static class StepContainer { - List 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.source = StepOptions.fromTask(project, task); - return step; - } - } - - public static class StepOptions { - @Expose List test; - @Expose String title; - @Expose List files; - @Expose List text; - @Expose List> samples; - @Expose Integer executionMemoryLimit; - @Expose Integer executionTimeLimit; - @Expose LinkedTreeMap codeTemplates; - @SerializedName("format_version") - @Expose public int formatVersion = 3; - @SerializedName("last_subtask_index") - @Expose int lastSubtaskIndex = 0; - - public static StepOptions fromTask(final Project project, @NotNull final Task task) { - final StepOptions source = new StepOptions(); - source.lastSubtaskIndex = task instanceof TaskWithSubtasks ? ((TaskWithSubtasks)task).getLastSubtaskIndex() : 0; - setTests(task, source, project); - setTaskTexts(task, source); - source.files = new ArrayList<>(); - source.title = task.getName(); - for (final Map.Entry entry : task.getTaskFiles().entrySet()) { - ApplicationManager.getApplication().runWriteAction(() -> { - final VirtualFile taskDir = task.getTaskDir(project); - assert taskDir != null; - String name = entry.getKey(); - VirtualFile answerFile = taskDir.findFileByRelativePath(name); - TaskFile studentTaskFile = EduUtils.createStudentFile(project, answerFile, null, 0); - if (studentTaskFile == null) { - return; - } - source.files.add(studentTaskFile); - }); - } - return source; - } - - private static void setTaskTexts(@NotNull Task task, @NotNull StepOptions stepOptions) { - stepOptions.text = new ArrayList<>(); - for (Map.Entry entry : task.getTaskTexts().entrySet()) { - stepOptions.text.add(new FileWrapper(entry.getKey(), entry.getValue())); - } - } - - private static void setTests(@NotNull Task task, @NotNull StepOptions source, @NotNull Project project) { - final Map testsText = task.getTestsText(); - source.test = new ArrayList<>(); - if (testsText.isEmpty()) { - List testFiles = getTestFiles(task, project); - for (VirtualFile testFile : testFiles) { - addFileWrapper(testFile, source.test); - } - } - else { - for (Map.Entry entry : testsText.entrySet()) { - source.test.add(new FileWrapper(entry.getKey(), entry.getValue())); - } - } - } - } - - private static void addFileWrapper(@NotNull VirtualFile file, List wrappers) { - try { - wrappers.add(new FileWrapper(file.getName(), VfsUtilCore.loadText(file))); - } - catch (IOException e) { - LOG.error(e); - } - } - - private static List getTestFiles(@NotNull Task task, @NotNull Project project) { - final Course course = task.getLesson().getCourse(); - final Language language = course.getLanguageById(); - final EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(language); - List testFiles = new ArrayList<>(); - VirtualFile taskDir = task.getTaskDir(project); - if (taskDir == null) { - return testFiles; - } - if (!(task instanceof TaskWithSubtasks)) { - VirtualFile testFile = taskDir.findChild(configurator.getTestFileName()); - testFiles.add(testFile); - return testFiles; - } - testFiles.addAll(Arrays.stream(taskDir.getChildren()) - .filter(file -> StudyUtils.isTestsFile(project, file.getName())) - .collect(Collectors.toList())); - return testFiles; - } - - public static class CoursesContainer { - public List courses; - public Map meta; - } - - public static class StepSourceWrapper { - @Expose - StepSource stepSource; - - public StepSourceWrapper(Project project, Task task, int lessonId) { - stepSource = new StepSource(project, task, lessonId); - } - } - - public static class CourseWrapper { - RemoteCourse course; - - public CourseWrapper(Course course) { - this.course = new RemoteCourse(); - this.course.setName(course.getName()); - this.course.setLanguage(course.getLanguageID()); - this.course.setDescription(course.getDescription()); - this.course.setAuthors(course.getAuthors()); - } - } - - public static class LessonWrapper { - Lesson lesson; - - public LessonWrapper(Lesson lesson) { - this.lesson = new Lesson(); - this.lesson.setName(lesson.getName()); - this.lesson.setId(lesson.getId()); - this.lesson.steps = new ArrayList<>(); - this.lesson.setPublic(true); - } - } - - static class LessonContainer { - List lessons; - } - - static class StepSource { - @Expose Step block; - @Expose int position = 0; - @Expose int lesson = 0; - Date update_date; - - public StepSource(Project project, Task task, int lesson) { - this.lesson = lesson; - position = task.getIndex(); - block = Step.fromTask(project, task); - } - } - - static class FileWrapper { - @Expose public final String name; - @Expose public final String text; - - public FileWrapper(String name, String text) { - this.name = name; - this.text = text; - } - } - - public static class Section { - List units; - int course; - String title; - - public int getId() { - return id; - } - - public void setCourse(int course) { - this.course = course; - } - - public void setTitle(String title) { - this.title = title; - } - - public void setPosition(int position) { - this.position = position; - } - - int position; - int id; - - public int getCourse() { - return course; - } - } - - public static class SectionWrapper { - Section section; - - public void setSection(Section section) { - this.section = section; - } - } - - public static class SectionContainer { - List
sections; - List lessons; - - List units; - - public List
getSections() { - return sections; - } - } - - public static class Unit { - int id; - int section; - int lesson; - int position; - List assignments; - - public void setSection(int section) { - this.section = section; - } - - public void setPosition(int position) { - this.position = position; - } - - public void setLesson(int lesson) { - this.lesson = lesson; - } - - public int getSection() { - return section; - } - - public int getId() { - return id; - } - } - - static class UnitContainer { - - List units; - } - - public static class UnitWrapper { - Unit unit; - - public void setUnit(Unit unit) { - this.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 AdaptiveAttemptWrapper { - static class Attempt { - int step; - Dataset dataset; - String dataset_url; - String status; - String time; - String time_left; - String user; - String user_id; - int id; - - public Attempt(int step) { - this.step = step; - } - - public boolean isActive() { - return status.equals("active"); - } - } - - static class Dataset { - boolean is_multiple_choice; - List options; - } - - public AdaptiveAttemptWrapper(int step) { - attempt = new Attempt(step); - } - - Attempt attempt; - } - - static class AdaptiveAttemptContainer { - List attempts; - } - - static class AttemptContainer { - List attempts; - } - - static class SolutionFile { - String name; - String text; - - public SolutionFile(String name, String text) { - this.name = name; - this.text = text; - } - } - - static class AuthorWrapper { - List users; - } - - static class SubmissionWrapper { - Submission submission; - - - public SubmissionWrapper(int attempt, String score, ArrayList files) { - submission = new Submission(score, attempt, files); - } - - static class Submission { - int attempt; - private final Reply reply; - - public Submission(String score, int attempt, ArrayList files) { - reply = new Reply(files, score); - this.attempt = attempt; - } - - static class Reply { - String score; - List solution; - - public Reply(ArrayList files, String score) { - this.score = score; - solution = files; - } - } - } - } - - 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.CodeReply(language, code)); - } - - public SubmissionToPostWrapper(@NotNull String attemptId, boolean[] choices) { - submission = new Submission(attemptId, new Submission.ChoiceReply(choices)); - } - - static class Submission { - String attempt; - Reply reply; - - public Submission(String attempt, Reply reply) { - this.attempt = attempt; - this.reply = reply; - } - - - interface Reply { - - } - - static class CodeReply implements Reply { - String language; - String code; - - public CodeReply(String language, String code) { - this.language = language; - this.code = code; - } - } - - static class ChoiceReply implements Reply { - boolean[] choices; - - public ChoiceReply(boolean[] choices) { - this.choices = choices; - } - } - } - } - - static class ResultSubmissionWrapper { - ResultSubmission[] submissions; - - static class ResultSubmission { - int id; - String status; - String hint; - } - } - - static class AssignmentsWrapper { - List 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; - } - } - - static class Enrollment { - String course; - - public Enrollment(String courseId) { - course = courseId; - } - } - - static class EnrollmentWrapper { - Enrollment enrollment; - - public EnrollmentWrapper(@NotNull final String courseId) { - enrollment = new Enrollment(courseId); - } - } - - static class TokenInfo { - @Expose String accessToken; - @Expose String refreshToken; - @Expose String tokenType; - @Expose String scope; - @Expose int expiresIn; - - public TokenInfo() { - accessToken = ""; - refreshToken = ""; - } - - public String getAccessToken() { - return accessToken; - } - - public String getRefreshToken() { - return refreshToken; - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/twitter/StudyTwitterAction.java b/python/educational-core/src/com/jetbrains/edu/learning/twitter/StudyTwitterAction.java deleted file mode 100644 index 360c0eba54bd..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/twitter/StudyTwitterAction.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.jetbrains.edu.learning.twitter; - -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.checker.StudyCheckListener; -import com.jetbrains.edu.learning.StudyTwitterPluginConfigurator; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; - -public class StudyTwitterAction implements StudyCheckListener { - - private StudyStatus myStatusBeforeCheck; - - @Override - public void beforeCheck(@NotNull Project project, @NotNull Task task) { - myStatusBeforeCheck = task.getStatus(); - } - - @Override - public void afterCheck(@NotNull Project project, @NotNull Task task) { - for (StudyTwitterPluginConfigurator twitterPluginConfigurator : Extensions.getExtensions(StudyTwitterPluginConfigurator.EP_NAME)) { - if (twitterPluginConfigurator.askToTweet(project, task, myStatusBeforeCheck)) { - StudyTwitterUtils.createTwitterDialogAndShow(project, twitterPluginConfigurator, task); - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java deleted file mode 100644 index 2ec3d193b3a2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java +++ /dev/null @@ -1,295 +0,0 @@ -package com.jetbrains.edu.learning.twitter; - -import com.intellij.ide.BrowserUtil; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.InputValidatorEx; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.components.JBScrollPane; -import com.jetbrains.edu.learning.StudyTwitterPluginConfigurator; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.apache.http.HttpStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import twitter4j.StatusUpdate; -import twitter4j.Twitter; -import twitter4j.TwitterException; -import twitter4j.TwitterFactory; -import twitter4j.auth.AccessToken; -import twitter4j.auth.RequestToken; -import twitter4j.conf.ConfigurationBuilder; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import java.awt.*; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - - -public class StudyTwitterUtils { - private static final Logger LOG = Logger.getInstance(StudyTwitterUtils.class); - - /** - * Set consumer key and secret. - * @return Twitter instance with consumer key and secret set. - */ - @NotNull - public static Twitter getTwitter(@NotNull final String consumerKey, @NotNull final String consumerSecret) { - ConfigurationBuilder configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.setOAuthConsumerKey(consumerKey); - configurationBuilder.setOAuthConsumerSecret(consumerSecret); - return new TwitterFactory(configurationBuilder.build()).getInstance(); - } - - /** - * Set access token and token secret in Twitter instance - */ - private static void setAuthInfoInTwitter(Twitter twitter, @NotNull String accessToken, - @NotNull String tokenSecret) { - AccessToken token = new AccessToken(accessToken, tokenSecret); - twitter.setOAuthAccessToken(token); - } - - public static void createTwitterDialogAndShow(@NotNull Project project, - @NotNull final StudyTwitterPluginConfigurator configurator, - @NotNull Task task) { - ApplicationManager.getApplication().invokeLater(() -> { - DialogWrapper.DoNotAskOption doNotAskOption = createDoNotAskOption(project, configurator); - StudyTwitterUtils.TwitterDialogPanel panel = configurator.getTweetDialogPanel(task); - if (panel != null) { - TwitterDialogWrapper wrapper = new TwitterDialogWrapper(project, panel, doNotAskOption); - wrapper.setDoNotAskOption(doNotAskOption); - panel.addTextFieldVerifier(createTextFieldLengthDocumentListener(wrapper, panel)); - - if (wrapper.showAndGet()) { - try { - boolean isAuthorized = !configurator.getTwitterAccessToken(project).isEmpty(); - Twitter twitter = getTwitter(configurator.getConsumerKey(project), configurator.getConsumerSecret(project)); - if (!isAuthorized) { - authorizeAndUpdateStatus(project, twitter, panel); - } - else { - setAuthInfoInTwitter(twitter, configurator.getTwitterAccessToken(project), configurator.getTwitterTokenSecret(project)); - updateStatus(panel, twitter); - } - } - catch (TwitterException | IOException e) { - LOG.warn(e.getMessage()); - Messages.showErrorDialog("Status wasn\'t updated. Please, check internet connection and try again", "Twitter"); - } - } - else { - LOG.warn("Panel is null"); - } - } - }); - } - - - private static DialogWrapper.DoNotAskOption createDoNotAskOption(@NotNull final Project project, - @NotNull final StudyTwitterPluginConfigurator configurator) { - return new DialogWrapper.DoNotAskOption() { - @Override - public boolean isToBeShown() { - return true; - } - - @Override - public void setToBeShown(boolean toBeShown, int exitCode) { - if (exitCode == DialogWrapper.CANCEL_EXIT_CODE || exitCode == DialogWrapper.OK_EXIT_CODE) { - configurator.setAskToTweet(project, toBeShown); - } - } - - @Override - public boolean canBeHidden() { - return true; - } - - @Override - public boolean shouldSaveOptionsOnCancel() { - return true; - } - - @NotNull - @Override - public String getDoNotShowMessage() { - return "Never ask me to tweet"; - } - }; - } - - /** - * Post on twitter media and text from panel - * @param panel shown to user and used to provide data to post - */ - public static void updateStatus(StudyTwitterUtils.TwitterDialogPanel panel, Twitter twitter) throws IOException, TwitterException { - StatusUpdate update = new StatusUpdate(panel.getMessage()); - InputStream e = panel.getMediaSource(); - if (e != null) { - File imageFile = FileUtil.createTempFile("twitter_media", panel.getMediaExtension()); - FileUtil.copy(e, new FileOutputStream(imageFile)); - update.media(imageFile); - } - - twitter.updateStatus(update); - BrowserUtil.browse("https://twitter.com/"); - } - - /** - * Show twitter dialog, asking user to tweet about his achievements. Post tweet with provided by panel - * media and text. - * As a result of succeeded tweet twitter website is opened in default browser. - */ - public static void authorizeAndUpdateStatus(@NotNull final Project project, @NotNull final Twitter twitter, - @NotNull final StudyTwitterUtils.TwitterDialogPanel panel) throws TwitterException { - RequestToken requestToken = twitter.getOAuthRequestToken(); - BrowserUtil.browse(requestToken.getAuthorizationURL()); - - ApplicationManager.getApplication().invokeLater(() -> { - String pin = createAndShowPinDialog(); - if (pin != null) { - try { - AccessToken token = twitter.getOAuthAccessToken(requestToken, pin); - StudyTwitterPluginConfigurator configurator = StudyUtils.getTwitterConfigurator(project); - if (configurator != null) { - configurator.storeTwitterTokens(project, token.getToken(), token.getTokenSecret()); - updateStatus(panel, twitter); - } - else { - LOG.warn("No twitter configurator is provided for the plugin"); - } - } - catch (TwitterException e) { - if (e.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { - LOG.warn("Unable to get the access token."); - LOG.warn(e.getMessage()); - } - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - } - }); - } - - public static String createAndShowPinDialog() { - return Messages.showInputDialog("Twitter PIN:", "Twitter Authorization", null, "", new InputValidatorEx() { - @Nullable - @Override - public String getErrorText(String inputString) { - inputString = inputString.trim(); - if (inputString.isEmpty()) { - return "PIN shouldn't be empty."; - } - if (!isNumeric(inputString)) { - return "PIN should be numeric."; - } - return null; - } - - @Override - public boolean checkInput(String inputString) { - return getErrorText(inputString) == null; - } - - @Override - public boolean canClose(String inputString) { - return true; - } - - private boolean isNumeric(@NotNull final String string) { - for (char c: string.toCharArray()) { - if (!StringUtil.isDecimalDigit(c)) { - return false; - } - } - return true; - } - }); - } - - /** - * Listener updates label indicating remaining symbols number like in twitter. - */ - private static DocumentListener createTextFieldLengthDocumentListener(@NotNull TwitterDialogWrapper builder, @NotNull final StudyTwitterUtils.TwitterDialogPanel panel) { - return new DocumentAdapter() { - @Override - protected void textChanged(DocumentEvent e) { - int length = e.getDocument().getLength(); - if (length > 140 || length == 0) { - builder.setOKActionEnabled(false); - panel.getRemainSymbolsLabel().setText("" + String.valueOf(140 - length) + ""); - } else { - builder.setOKActionEnabled(true); - panel.getRemainSymbolsLabel().setText(String.valueOf(140 - length)); - } - - } - }; - } - - /** - * Dialog wrapper class with DoNotAsl option for asking user to tweet. - * */ - private static class TwitterDialogWrapper extends DialogWrapper { - private final StudyTwitterUtils.TwitterDialogPanel myPanel; - - TwitterDialogWrapper(@Nullable Project project, @NotNull StudyTwitterUtils.TwitterDialogPanel panel, DoNotAskOption doNotAskOption) { - super(project); - setTitle("Twitter"); - setDoNotAskOption(doNotAskOption); - setOKButtonText("Tweet"); - setCancelButtonText("No"); - setResizable(true); - Dimension preferredSize = panel.getPreferredSize(); - setSize((int) preferredSize.getHeight(), (int) preferredSize.getWidth()); - myPanel = panel; - init(); - } - - @Nullable - @Override - protected JComponent createCenterPanel() { - return new JBScrollPane(myPanel); - } - } - - /** - * Class provides structure for twitter dialog panel - */ - public abstract static class TwitterDialogPanel extends JPanel { - - /** - * Provides tweet text - */ - @NotNull public abstract String getMessage(); - - /** - * @return Input stream of media should be posted or null if there's nothing to post - */ - @Nullable public abstract InputStream getMediaSource(); - - @Nullable public abstract String getMediaExtension(); - - /** - * @return label that will be used to show remained symbol number - */ - @NotNull public abstract JLabel getRemainSymbolsLabel(); - - /** - * Api to add document listener to field containing tweet text - */ - public abstract void addTextFieldVerifier(@NotNull final DocumentListener documentListener); - - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/AuthorizationPanel.form b/python/educational-core/src/com/jetbrains/edu/learning/ui/AuthorizationPanel.form deleted file mode 100644 index c4d23b0653d6..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/AuthorizationPanel.form +++ /dev/null @@ -1,39 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/AuthorizationPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/AuthorizationPanel.java deleted file mode 100644 index db492533650a..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/AuthorizationPanel.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.jetbrains.edu.learning.ui; - -import com.intellij.ui.components.JBTextField; - -import javax.swing.*; - -public class AuthorizationPanel { - private JPanel myContentPanel; - private JBTextField myCodeTextField; - - public JPanel getContentPanel() { - return myContentPanel; - } - - public String getCode() { - return myCodeTextField.getText(); - } - - public JComponent getPreferableFocusComponent() { - return myCodeTextField; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/CCCreateAnswerPlaceholderPanel.form b/python/educational-core/src/com/jetbrains/edu/learning/ui/CCCreateAnswerPlaceholderPanel.form deleted file mode 100644 index bf06cb993c46..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/CCCreateAnswerPlaceholderPanel.form +++ /dev/null @@ -1,106 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/CCCreateAnswerPlaceholderPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/CCCreateAnswerPlaceholderPanel.java deleted file mode 100644 index 5dd63e2c5943..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/CCCreateAnswerPlaceholderPanel.java +++ /dev/null @@ -1,222 +0,0 @@ -package com.jetbrains.edu.learning.ui; - -import com.intellij.icons.AllIcons; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.ui.ColorUtil; -import com.intellij.ui.JBColor; -import com.intellij.ui.components.JBLabel; -import com.intellij.uiDesigner.core.GridLayoutManager; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; -import java.util.ArrayList; -import java.util.List; - -public class CCCreateAnswerPlaceholderPanel { - private static final String NEXT_HINT = "Next Hint"; - private static final String PREVIOUS_HINT = "Previous Hint"; - private static final String ADD_HINT = "Add Hint"; - private static final String REMOVE_HINT = "Remove Hint"; - private static final String HINT_PLACEHOLDER = "Type here to add hint"; - - private JPanel myPanel; - private JTextArea myHintTextArea; - private JPanel myHintsPanel; - private JBLabel myHintLabel; - private JPanel actionsPanel; - private JTextArea myPlaceholderTextArea; - private List myHints = new ArrayList<>(); - private int myShownHintNumber = 0; - - public CCCreateAnswerPlaceholderPanel(@Nullable String placeholderText, @NotNull List hints) { - if (hints.isEmpty()) { - myHints.add(HINT_PLACEHOLDER); - } - else { - myHints.addAll(hints); - } - - myPlaceholderTextArea.setBorder(BorderFactory.createLineBorder(JBColor.border())); - myPlaceholderTextArea.setText(placeholderText); - myPlaceholderTextArea.addFocusListener(new FocusAdapter() { - @Override - public void focusGained(FocusEvent e) { - myPlaceholderTextArea.selectAll(); - } - }); - myPlaceholderTextArea.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null); - myPlaceholderTextArea.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null); - - myHintsPanel.setBorder(BorderFactory.createLineBorder(JBColor.border())); - ((GridLayoutManager)myHintsPanel.getLayout()).setHGap(1); - - myHintTextArea.setFont(UIUtil.getLabelFont()); - myPlaceholderTextArea.setFont(UIUtil.getLabelFont()); - myHintTextArea.addFocusListener(createFocusListenerToSetDefaultHintText()); - - actionsPanel.add(createHintToolbarComponent(), BorderLayout.WEST); - showHint(); - } - - @NotNull - private FocusAdapter createFocusListenerToSetDefaultHintText() { - return new FocusAdapter() { - @Override - public void focusGained(FocusEvent e) { - if (myHintTextArea.getText().equals(HINT_PLACEHOLDER)) { - myHintTextArea.setForeground(UIUtil.getActiveTextColor()); - myHintTextArea.setText(""); - } - } - - @Override - public void focusLost(FocusEvent e) { - if (myShownHintNumber == 0 && myHintTextArea.getText().isEmpty()) { - myHintTextArea.setForeground(UIUtil.getInactiveTextColor()); - myHintTextArea.setText(HINT_PLACEHOLDER); - } - } - }; - } - - private JComponent createHintToolbarComponent() { - final DefaultActionGroup addRemoveGroup = new DefaultActionGroup(); - addRemoveGroup.addAll(new AddHint(), new RemoveHint(), new ShowNext(), new ShowPrevious()); - return ActionManager.getInstance().createActionToolbar("Hint", addRemoveGroup, false).getComponent(); - } - - private void updateHintNumberLabel() { - if (myHints.size() > 1) { - final String color = String.valueOf(ColorUtil.toHex(UIUtil.getHeaderInactiveColor())); - myHintLabel.setText(UIUtil.toHtml("Hint" + " (" + (myShownHintNumber + 1) + "/" + myHints.size() + "):")); - } - else { - myHintLabel.setText("Hint: "); - } - } - - public void showHint() { - String hintText = myHints.get(myShownHintNumber); - if (hintText.equals(HINT_PLACEHOLDER)) { - myHintTextArea.setForeground(UIUtil.getInactiveTextColor()); - } - else { - myHintTextArea.setForeground(UIUtil.getActiveTextColor()); - } - - myHintTextArea.setText(hintText); - updateHintNumberLabel(); - } - - public String getAnswerPlaceholderText() { - return myPlaceholderTextArea.getText(); - } - - public List getHints() { - final String hintText = myHintTextArea.getText(); - if (myShownHintNumber == 0 && hintText.equals(HINT_PLACEHOLDER)) { - myHints.remove(0); - } - else { - myHints.set(myShownHintNumber, hintText); - } - - return myHints; - } - - public JComponent getPreferredFocusedComponent() { - return myPlaceholderTextArea; - } - - public JPanel getMailPanel() { - return myPanel; - } - - private class ShowNext extends AnAction { - - public ShowNext() { - super(NEXT_HINT, NEXT_HINT, AllIcons.Actions.Forward); - } - - @Override - public void actionPerformed(AnActionEvent e) { - myHints.set(myShownHintNumber, myHintTextArea.getText()); - myShownHintNumber++; - showHint(); - } - - @Override - public void update(AnActionEvent e) { - e.getPresentation().setEnabled(myShownHintNumber + 1 < myHints.size()); - } - } - - private class ShowPrevious extends AnAction { - - public ShowPrevious() { - super(PREVIOUS_HINT, PREVIOUS_HINT, AllIcons.Actions.Back); - } - - @Override - public void actionPerformed(AnActionEvent e) { - myHints.set(myShownHintNumber, myHintTextArea.getText()); - myShownHintNumber--; - showHint(); - } - - @Override - public void update(AnActionEvent e) { - e.getPresentation().setEnabled(myShownHintNumber - 1 >= 0); - } - } - - private class AddHint extends AnAction { - - public AddHint() { - super(ADD_HINT, ADD_HINT, AllIcons.General.Add); - } - - @Override - public void actionPerformed(AnActionEvent e) { - myHints.set(myShownHintNumber, myHintTextArea.getText()); - myHints.add(++myShownHintNumber, ""); - showHint(); - } - } - - private class RemoveHint extends AnAction { - - public RemoveHint() { - super(REMOVE_HINT, REMOVE_HINT, AllIcons.General.Remove); - } - - @Override - public void actionPerformed(AnActionEvent e) { - myHints.remove(myShownHintNumber); - if (myHints.isEmpty()) { - myHints.add(HINT_PLACEHOLDER); - myHintTextArea.transferFocus(); - showHint(); - } - else { - myShownHintNumber += myShownHintNumber < myHints.size() ? 0 : -1; - showHint(); - } - } - - @Override - public void update(AnActionEvent e) { - final boolean hasOnlyMockHint = myShownHintNumber == 0 && myHints.get(myShownHintNumber).equals(HINT_PLACEHOLDER); - final boolean hasMoreHints = myHints.size() > 0 && !hasOnlyMockHint; - e.getPresentation().setEnabled(hasMoreHints); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyBrowserWindow.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyBrowserWindow.java deleted file mode 100644 index 065229ab8053..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyBrowserWindow.java +++ /dev/null @@ -1,363 +0,0 @@ -package com.jetbrains.edu.learning.ui; - -import com.intellij.icons.AllIcons; -import com.intellij.ide.BrowserUtil; -import com.intellij.ide.ui.LafManager; -import com.intellij.ide.ui.LafManagerListener; -import com.intellij.ide.ui.laf.darcula.DarculaLookAndFeelInfo; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.StreamUtil; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.navigation.StudyNavigator; -import com.sun.webkit.dom.ElementImpl; -import javafx.application.Platform; -import javafx.concurrent.Worker; -import javafx.embed.swing.JFXPanel; -import javafx.scene.Scene; -import javafx.scene.control.ProgressBar; -import javafx.scene.layout.StackPane; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebHistory; -import javafx.scene.web.WebView; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.w3c.dom.*; -import org.w3c.dom.events.Event; -import org.w3c.dom.events.EventListener; -import org.w3c.dom.events.EventTarget; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class StudyBrowserWindow extends JFrame { - private static final Logger LOG = Logger.getInstance(StudyToolWindow.class); - private static final String EVENT_TYPE_CLICK = "click"; - private static final Pattern IN_COURSE_LINK = Pattern.compile("#(\\w+)#(\\w+)#"); - private JFXPanel myPanel; - private WebView myWebComponent; - private StackPane myPane; - - private WebEngine myEngine; - private ProgressBar myProgressBar; - private final Project myProject; - private boolean myLinkInNewBrowser = true; - private boolean myShowProgress = false; - - public StudyBrowserWindow(@NotNull final Project project, final boolean linkInNewWindow, final boolean showProgress) { - myProject = project; - myLinkInNewBrowser = linkInNewWindow; - myShowProgress = showProgress; - setSize(new Dimension(900, 800)); - setLayout(new BorderLayout()); - setPanel(new JFXPanel()); - setTitle("Study Browser"); - LafManager.getInstance().addLafManagerListener(new StudyLafManagerListener()); - initComponents(); - } - - private void updateLaf(boolean isDarcula) { - if (isDarcula) { - updateLafDarcula(); - } - else { - updateIntellijAndGTKLaf(); - } - } - - private void updateIntellijAndGTKLaf() { - Platform.runLater(() -> { - final URL scrollBarStyleUrl = getClass().getResource("/style/javaFXBrowserScrollBar.css"); - final URL engineStyleUrl = getClass().getResource("/style/javaFXBrowser.css"); - myPane.getStylesheets().add(scrollBarStyleUrl.toExternalForm()); - myEngine.setUserStyleSheetLocation(engineStyleUrl.toExternalForm()); - myPanel.getScene().getStylesheets().add(engineStyleUrl.toExternalForm()); - myEngine.reload(); - }); - } - - private void updateLafDarcula() { - Platform.runLater(() -> { - final URL engineStyleUrl = getClass().getResource("/style/javaFXBrowserDarcula.css"); - final URL scrollBarStyleUrl = getClass().getResource("/style/javaFXBrowserDarculaScrollBar.css"); - myEngine.setUserStyleSheetLocation(engineStyleUrl.toExternalForm()); - myPane.getStylesheets().add(scrollBarStyleUrl.toExternalForm()); - myPane.setStyle("-fx-background-color: #3c3f41"); - myPanel.getScene().getStylesheets().add(engineStyleUrl.toExternalForm()); - myEngine.reload(); - }); - } - - private void initComponents() { - Platform.runLater(() -> { - myPane = new StackPane(); - myWebComponent = new WebView(); - myWebComponent.setOnDragDetected(event -> {}); - myEngine = myWebComponent.getEngine(); - - - if (myShowProgress) { - myProgressBar = makeProgressBarWithListener(); - myWebComponent.setVisible(false); - myPane.getChildren().addAll(myWebComponent, myProgressBar); - } - else { - myPane.getChildren().add(myWebComponent); - } - if (myLinkInNewBrowser) { - initHyperlinkListener(); - } - Scene scene = new Scene(myPane); - myPanel.setScene(scene); - myPanel.setVisible(true); - updateLaf(LafManager.getInstance().getCurrentLookAndFeel() instanceof DarculaLookAndFeelInfo); - }); - - add(myPanel, BorderLayout.CENTER); - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - } - - public void loadContent(@NotNull final String content) { - Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course == null) { - return; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator == null) { - Platform.runLater(() -> myEngine.loadContent(content)); - } - else { - String withCodeHighlighting = createHtmlWithCodeHighlighting(content, configurator.getLanguageScriptUrl(), configurator.getDefaultHighlightingMode()); - Platform.runLater(() -> { - updateLookWithProgressBarIfNeeded(); - myEngine.loadContent(withCodeHighlighting); - }); - } - } - - @Nullable - private String createHtmlWithCodeHighlighting(@NotNull final String content, - @NotNull String languageScriptUrl, - @NotNull String defaultHighlightingMode) { - String template = null; - InputStream stream = getClass().getResourceAsStream("/code-mirror/template.html"); - try { - template = StreamUtil.readText(stream, "utf-8"); - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - finally { - try { - stream.close(); - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - } - - if (template == null) { - LOG.warn("Code mirror template is null"); - return null; - } - - final EditorColorsScheme editorColorsScheme = EditorColorsManager.getInstance().getGlobalScheme(); - int fontSize = editorColorsScheme.getEditorFontSize(); - - template = template.replace("${font_size}", String.valueOf(fontSize- 2)); - template = template.replace("${codemirror}", getClass().getResource("/code-mirror/codemirror.js").toExternalForm()); - template = template.replace("${language_script}", languageScriptUrl); - template = template.replace("${default_mode}", defaultHighlightingMode); - template = template.replace("${runmode}", getClass().getResource("/code-mirror/runmode.js").toExternalForm()); - template = template.replace("${colorize}", getClass().getResource("/code-mirror/colorize.js").toExternalForm()); - template = template.replace("${javascript}", getClass().getResource("/code-mirror/javascript.js").toExternalForm()); - if (LafManager.getInstance().getCurrentLookAndFeel() instanceof DarculaLookAndFeelInfo) { - template = template.replace("${css_oldcodemirror}", getClass().getResource("/code-mirror/codemirror-old-darcula.css").toExternalForm()); - template = template.replace("${css_codemirror}", getClass().getResource("/code-mirror/codemirror-darcula.css").toExternalForm()); - } - else { - template = template.replace("${css_oldcodemirror}", getClass().getResource("/code-mirror/codemirror-old.css").toExternalForm()); - template = template.replace("${css_codemirror}", getClass().getResource("/code-mirror/codemirror.css").toExternalForm()); - } - template = template.replace("${code}", content); - - return template; - } - - private void updateLookWithProgressBarIfNeeded() { - if (myShowProgress) { - myProgressBar.setVisible(true); - myWebComponent.setVisible(false); - } - } - - private void initHyperlinkListener() { - myEngine.getLoadWorker().stateProperty().addListener((ov, oldState, newState) -> { - if (newState == Worker.State.SUCCEEDED) { - final EventListener listener = makeHyperLinkListener(); - - addListenerToAllHyperlinkItems(listener); - } - }); - } - - private void addListenerToAllHyperlinkItems(EventListener listener) { - final Document doc = myEngine.getDocument(); - if (doc != null) { - final NodeList nodeList = doc.getElementsByTagName("a"); - for (int i = 0; i < nodeList.getLength(); i++) { - ((EventTarget)nodeList.item(i)).addEventListener(EVENT_TYPE_CLICK, listener, false); - } - } - } - - @NotNull - private EventListener makeHyperLinkListener() { - return new EventListener() { - @Override - public void handleEvent(Event ev) { - String domEventType = ev.getType(); - if (domEventType.equals(EVENT_TYPE_CLICK)) { - ev.preventDefault(); - Element target = (Element)ev.getTarget(); - String hrefAttribute = getElementWithATag(target).getAttribute("href"); - - if (hrefAttribute != null) { - final Matcher matcher = IN_COURSE_LINK.matcher(hrefAttribute); - if (matcher.matches()) { - final String lessonName = matcher.group(1); - final String taskName = matcher.group(2); - StudyNavigator.navigateToTask(myProject, lessonName, taskName); - } - else { - myEngine.setJavaScriptEnabled(true); - myEngine.getLoadWorker().cancel(); - final String href = getLink(target); - if (href == null) return; - BrowserUtil.browse(href); - } - } - } - } - - private Element getElementWithATag(Element element) { - Element currentElement = element; - while (!currentElement.getTagName().toLowerCase(Locale.ENGLISH).equals("a")) { - currentElement = ((ElementImpl)currentElement).getParentElement(); - } - return currentElement; - } - - @Nullable - private String getLink(@NotNull Element element) { - final String href = element.getAttribute("href"); - return href == null ? getLinkFromNodeWithCodeTag(element) : href; - } - - @Nullable - private String getLinkFromNodeWithCodeTag(@NotNull Element element) { - Node parentNode = element.getParentNode(); - NamedNodeMap attributes = parentNode.getAttributes(); - while (attributes.getLength() > 0 && attributes.getNamedItem("class") != null) { - parentNode = parentNode.getParentNode(); - attributes = parentNode.getAttributes(); - } - return attributes.getNamedItem("href").getNodeValue(); - } - }; - } - - public void addBackAndOpenButtons() { - ApplicationManager.getApplication().invokeLater(() -> { - final JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); - - final JButton backButton = makeGoButton("Click to go back", AllIcons.Actions.Back, -1); - final JButton forwardButton = makeGoButton("Click to go forward", AllIcons.Actions.Forward, 1); - final JButton openInBrowser = new JButton(AllIcons.Actions.Browser_externalJavaDoc); - openInBrowser.addActionListener(e -> BrowserUtil.browse(myEngine.getLocation())); - openInBrowser.setToolTipText("Click to open link in browser"); - addButtonsAvailabilityListeners(backButton, forwardButton); - - panel.setMaximumSize(new Dimension(40, getPanel().getHeight())); - panel.add(backButton); - panel.add(forwardButton); - panel.add(openInBrowser); - - add(panel, BorderLayout.PAGE_START); - }); - } - - private void addButtonsAvailabilityListeners(JButton backButton, JButton forwardButton) { - Platform.runLater(() -> myEngine.getLoadWorker().stateProperty().addListener((ov, oldState, newState) -> { - if (newState == Worker.State.SUCCEEDED) { - final WebHistory history = myEngine.getHistory(); - boolean isGoBackAvailable = history.getCurrentIndex() > 0; - boolean isGoForwardAvailable = history.getCurrentIndex() < history.getEntries().size() - 1; - ApplicationManager.getApplication().invokeLater(() -> { - backButton.setEnabled(isGoBackAvailable); - forwardButton.setEnabled(isGoForwardAvailable); - }); - } - })); - } - - private JButton makeGoButton(@NotNull final String toolTipText, @NotNull final Icon icon, final int direction) { - final JButton button = new JButton(icon); - button.setEnabled(false); - button.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 1) { - Platform.runLater(() -> myEngine.getHistory().go(direction)); - } - } - }); - button.setToolTipText(toolTipText); - return button; - } - - - private ProgressBar makeProgressBarWithListener() { - final ProgressBar progress = new ProgressBar(); - progress.progressProperty().bind(myWebComponent.getEngine().getLoadWorker().progressProperty()); - - myWebComponent.getEngine().getLoadWorker().stateProperty().addListener( - (ov, oldState, newState) -> { - if (myWebComponent.getEngine().getLocation().contains("http") && newState == Worker.State.SUCCEEDED) { - myProgressBar.setVisible(false); - myWebComponent.setVisible(true); - } - }); - - return progress; - } - - public JFXPanel getPanel() { - return myPanel; - } - - private void setPanel(JFXPanel panel) { - myPanel = panel; - } - - private class StudyLafManagerListener implements LafManagerListener { - @Override - public void lookAndFeelChanged(LafManager manager) { - updateLaf(manager.getCurrentLookAndFeel() instanceof DarculaLookAndFeelInfo); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyCondition.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyCondition.java deleted file mode 100644 index 4895bc04faf7..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyCondition.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.jetbrains.edu.learning.ui; - -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.util.Condition; - -/** - * author: liana - * data: 7/29/14. - */ -public class StudyCondition implements Condition, DumbAware { - @Override - public boolean value(Object o) { - return false; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyHint.kt b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyHint.kt deleted file mode 100644 index 01e2be6a8065..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyHint.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.jetbrains.edu.learning.ui - -import com.intellij.icons.AllIcons -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.DefaultActionGroup -import com.intellij.openapi.actionSystem.Presentation -import com.intellij.openapi.project.Project -import com.jetbrains.edu.coursecreator.actions.CCEditHintAction -import com.jetbrains.edu.learning.StudySettings -import com.jetbrains.edu.learning.StudyTaskManager -import com.jetbrains.edu.learning.StudyUtils -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder -import java.util.* - -open class StudyHint(private val myPlaceholder: AnswerPlaceholder?, - private val myProject: Project) { - - companion object { - private val OUR_WARNING_MESSAGE = "Put the caret in the answer placeholder to get hint" - private val HINTS_NOT_AVAILABLE = "There is no hint for this answer placeholder" - } - - val studyToolWindow: StudyToolWindow - protected var myShownHintNumber = 0 - protected var isEditingMode = false - - init { - if (StudyUtils.hasJavaFx() && StudySettings.getInstance().shouldUseJavaFx()) { - studyToolWindow = StudyJavaFxToolWindow() - } - else { - studyToolWindow = StudySwingToolWindow() - } - studyToolWindow.init(myProject, false) - - if (myPlaceholder == null) { - studyToolWindow.setText(OUR_WARNING_MESSAGE) - studyToolWindow.setActionToolbar(DefaultActionGroup()) - } - - val course = StudyTaskManager.getInstance(myProject).course - if (course != null) { - val group = DefaultActionGroup() - val hints = myPlaceholder?.hints - if (hints != null) { - group.addAll(Arrays.asList(GoBackward(), GoForward(), CCEditHintAction(myPlaceholder))) - studyToolWindow.setActionToolbar(group) - setHintText(hints) - } - } - } - - protected fun setHintText(hints: List) { - if (!hints.isEmpty()) { - studyToolWindow.setText(hints[myShownHintNumber]) - } - else { - myShownHintNumber = -1 - studyToolWindow.setText(HINTS_NOT_AVAILABLE) - } - } - - inner class GoForward : AnAction("Next Hint", "Next Hint", AllIcons.Actions.Forward) { - - - override fun actionPerformed(e: AnActionEvent) { - studyToolWindow.setText(myPlaceholder!!.hints[++myShownHintNumber]) - } - - override fun update(e: AnActionEvent) { - val presentation = e.presentation - updateVisibility(myPlaceholder, presentation) - presentation.isEnabled = !isEditingMode && myPlaceholder != null && myShownHintNumber + 1 < myPlaceholder.hints.size - } - } - - private fun updateVisibility(myPlaceholder: AnswerPlaceholder?, - presentation: Presentation) { - val hasMultipleHints = myPlaceholder != null && myPlaceholder.hints.size > 1 - presentation.isVisible = !StudyUtils.isStudentProject(myProject) || hasMultipleHints - } - - inner class GoBackward : AnAction("Previous Hint", "Previous Hint", AllIcons.Actions.Back) { - - override fun actionPerformed(e: AnActionEvent) { - studyToolWindow.setText(myPlaceholder!!.hints[--myShownHintNumber]) - } - - override fun update(e: AnActionEvent) { - val presentation = e.presentation - updateVisibility(myPlaceholder, presentation) - presentation.isEnabled = !isEditingMode && myShownHintNumber - 1 >= 0 - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java deleted file mode 100644 index 9856023fdabb..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java +++ /dev/null @@ -1,52 +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.ui; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.StudyUtils; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; - -public class StudyJavaFxToolWindow extends StudyToolWindow { - private StudyBrowserWindow myBrowserWindow; - - public StudyJavaFxToolWindow() { - super(); - } - - @Override - public JComponent createTaskInfoPanel(Project project) { - myBrowserWindow = new StudyBrowserWindow(project, true, false); - myBrowserWindow.addBackAndOpenButtons(); - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); - panel.add(myBrowserWindow.getPanel()); - return panel; - } - - @Override - public void updateFonts(@NotNull Project project) { - final String text = StudyUtils.getTaskText(project); - if (text != null) { - setText(text); - } - } - - @Override - public void setText(@NotNull String text) { - myBrowserWindow.loadContent(text); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java deleted file mode 100644 index 312631f83aa8..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ /dev/null @@ -1,354 +0,0 @@ -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.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.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.ui.ComboBox; -import com.intellij.openapi.ui.FixedSizeButton; -import com.intellij.openapi.ui.LabeledComponent; -import com.intellij.openapi.ui.VerticalFlowLayout; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.ui.popup.ListPopup; -import com.intellij.openapi.ui.popup.PopupStep; -import com.intellij.openapi.ui.popup.util.BaseListPopupStep; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.AncestorListenerAdapter; -import com.intellij.ui.PanelWithAnchor; -import com.intellij.ui.components.JBScrollPane; -import com.intellij.util.ui.UIUtil; -import com.jetbrains.edu.learning.StudySettings; -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.EduAdaptiveStepicConnector; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.OAuthDialog; -import com.jetbrains.edu.learning.stepic.StepicUser; -import icons.EducationalCoreIcons; -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; -import java.util.ArrayList; -import java.util.List; - -/** - * author: liana - * data: 7/31/14. - */ -public class StudyNewProjectPanel extends JPanel implements PanelWithAnchor { - private List myAvailableCourses = new ArrayList<>(); - private JButton myBrowseButton; - private ComboBox myCoursesComboBox; - private JButton myRefreshButton; - private JLabel myAuthorLabel; - private JPanel myInfoPanel; - private JTextPane myDescriptionPane; - private JComponent myAnchor; - private final StudyProjectGenerator myGenerator; - private boolean isLocal = false; - private static final String CONNECTION_ERROR = "Failed to download courses.
Check your Internet connection."; - private static final String INVALID_COURSE = "Selected course is invalid"; - private FacetValidatorsManager myValidationManager; - private boolean isComboboxInitialized; - private static final String LOGIN_TO_STEPIC_MESSAGE = "Login to Stepik to open the adaptive course "; - private static final String LOGIN_TO_STEPIC = "Login to Stepik"; - - public StudyNewProjectPanel(@NotNull final StudyProjectGenerator generator, boolean isLocal) { - super(new VerticalFlowLayout(true, true)); - myGenerator = generator; - this.isLocal = isLocal; - layoutPanel(); - initListeners(); - } - - private void layoutPanel() { - myCoursesComboBox = new ComboBox<>(); - - final LabeledComponent coursesCombo = LabeledComponent.create(myCoursesComboBox, "Courses:", BorderLayout.WEST); - - myRefreshButton = new FixedSizeButton(coursesCombo); - if (SystemInfo.isMac && !UIUtil.isUnderDarcula()) - myRefreshButton.putClientProperty("JButton.buttonType", null); - myRefreshButton.setIcon(AllIcons.Actions.Refresh); - myBrowseButton = new FixedSizeButton(coursesCombo); - - final JPanel comboPanel = new JPanel(new BorderLayout()); - comboPanel.add(coursesCombo, BorderLayout.CENTER); - comboPanel.add(myRefreshButton, BorderLayout.EAST); - - final JPanel coursesPanel = new JPanel(new BorderLayout()); - coursesPanel.add(comboPanel, BorderLayout.CENTER); - coursesPanel.add(myBrowseButton, BorderLayout.EAST); - - add(coursesPanel); - myAnchor = coursesCombo; - - final JPanel panel = new JPanel(new BorderLayout()); - final JLabel invisibleLabel = new JLabel(); - invisibleLabel.setPreferredSize(new JLabel("Location: ").getPreferredSize()); - panel.add(invisibleLabel, BorderLayout.WEST); - - myInfoPanel = new JPanel(new VerticalFlowLayout()); - myAuthorLabel = new JLabel(); - myDescriptionPane = new JTextPane(); - myDescriptionPane.setEditable(true); - myDescriptionPane.setEnabled(true); - myAuthorLabel.setEnabled(true); - myDescriptionPane.setPreferredSize(new Dimension(150, 200)); - myDescriptionPane.setFont(coursesCombo.getFont()); - myInfoPanel.add(myAuthorLabel); - myInfoPanel.add(new JBScrollPane(myDescriptionPane)); - - panel.add(myInfoPanel, BorderLayout.CENTER); - add(panel); - } - - private void initCoursesCombobox() { - myAvailableCourses = - myGenerator.getCoursesUnderProgress(!isLocal, "Getting Available Courses", ProjectManager.getInstance().getDefaultProject()); - if (myAvailableCourses.contains(Course.INVALID_COURSE)) { - setError(CONNECTION_ERROR); - } - else { - addCoursesToCombobox(myAvailableCourses); - final Course selectedCourse = StudyUtils.getFirst(myAvailableCourses); - if (selectedCourse == null) return; - setAuthors(selectedCourse); - myDescriptionPane.setText(selectedCourse.getDescription()); - myDescriptionPane.setEditable(false); - //setting the first course in list as selected - myGenerator.setSelectedCourse(selectedCourse); - if (myGenerator.getSelectedCourse() != null) { - myCoursesComboBox.setSelectedItem(myGenerator.getSelectedCourse()); - } - if (selectedCourse.isAdaptive() && !myGenerator.isLoggedIn()) { - setError(LOGIN_TO_STEPIC_MESSAGE); - } - else { - setOK(); - } - } - } - - private void setupBrowseButton() { - if (SystemInfo.isMac && !UIUtil.isUnderDarcula()) - myBrowseButton.putClientProperty("JButton.buttonType", null); - myBrowseButton.setIcon(EducationalCoreIcons.InterpreterGear); - final FileChooserDescriptor fileChooser = new FileChooserDescriptor(true, false, false, true, false, false) { - @Override - public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { - return file.isDirectory() || StudyUtils.isZip(file.getName()); - } - - @Override - public boolean isFileSelectable(VirtualFile file) { - return StudyUtils.isZip(file.getName()); - } - }; - myBrowseButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - final BaseListPopupStep popupStep = new BaseListPopupStep("", "Add local course", LOGIN_TO_STEPIC) { - @Override - public PopupStep onChosen(final String selectedValue, boolean finalChoice) { - return doFinalStep(() -> { - if ("Add local course".equals(selectedValue)) { - - Project[] projects = ProjectManager.getInstance().getOpenProjects(); - FileChooser.chooseFile(fileChooser, null, projects.length == 0 ? null : projects[0].getBaseDir(), - file -> { - String fileName = file.getPath(); - int oldSize = myAvailableCourses.size(); - Course courseInfo = myGenerator.addLocalCourse(fileName); - if (courseInfo != null) { - if (oldSize != myAvailableCourses.size()) { - myCoursesComboBox.addItem(courseInfo); - } - myCoursesComboBox.setSelectedItem(courseInfo); - setOK(); - } - else { - setError(INVALID_COURSE); - myCoursesComboBox.removeAllItems(); - myCoursesComboBox.addItem(Course.INVALID_COURSE); - for (Course course : myAvailableCourses) { - myCoursesComboBox.addItem(course); - } - myCoursesComboBox.setSelectedItem(Course.INVALID_COURSE); - } - }); - } - else if (LOGIN_TO_STEPIC.equals(selectedValue)) { - EduStepicConnector.doAuthorize(() -> showLoginDialog()); - StepicUser stepicUser = StudySettings.getInstance().getUser(); - if (stepicUser != null) { - ProgressManager.getInstance().runProcessWithProgressSynchronously( - () -> myGenerator.setEnrolledCoursesIds(EduAdaptiveStepicConnector.getEnrolledCoursesIds(stepicUser)), - "Getting Enrolled Courses", true, DefaultProjectFactory.getInstance().getDefaultProject()); - - final List courses = myGenerator.getCourses(true); - if (courses != null) { - ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses)); - } - } - - } - }); - } - }; - final ListPopup popup = JBPopupFactory.getInstance().createListPopup(popupStep); - popup.showInScreenCoordinates(myBrowseButton, myBrowseButton.getLocationOnScreen()); - } - }); - } - - public void showLoginDialog() { - OAuthDialog dialog = new OAuthDialog(); - if (dialog.showAndGet()) { - StepicUser stepicUser = dialog.getStepicUser(); - StudySettings.getInstance().setUser(stepicUser); - setOK(); - } - } - - private void initListeners() { - myRefreshButton.addActionListener(new RefreshActionListener()); - myCoursesComboBox.addActionListener(new CourseSelectedListener()); - setupBrowseButton(); - addAncestorListener(new AncestorListenerAdapter() { - @Override - public void ancestorMoved(AncestorEvent event) { - if (!isComboboxInitialized && isVisible()) { - isComboboxInitialized = true; - initCoursesCombobox(); - } - Course selectedCourse = (Course)myCoursesComboBox.getSelectedItem(); - if (selectedCourse == null || selectedCourse.equals(Course.INVALID_COURSE)) { - setError(CONNECTION_ERROR); - } - } - }); - } - - private void setError(@NotNull final String errorMessage) { - myGenerator.fireStateChanged(new ValidationResult(errorMessage)); - if (myValidationManager != null) { - myValidationManager.validate(); - } - } - - public void setOK() { - myGenerator.fireStateChanged(ValidationResult.OK); - if (myValidationManager != null) { - myValidationManager.validate(); - } - } - public void registerValidators(final FacetValidatorsManager manager) { - myValidationManager = manager; - } - - @Override - public JComponent getAnchor() { - return myAnchor; - } - - @Override - public void setAnchor(@Nullable JComponent anchor) { - myAnchor = anchor; - } - - public void updateInfoPanel(@NotNull Course selectedCourse) { - setAuthors(selectedCourse); - myDescriptionPane.setText(selectedCourse.getDescription()); - } - - /** - * Handles refreshing courses - * Old courses added to new courses only if their - * meta file still exists in local file system - */ - private class RefreshActionListener implements ActionListener { - @Override - public void actionPerformed(ActionEvent e) { - final List courses = - myGenerator.getCoursesUnderProgress(true, "Refreshing Course List", DefaultProjectFactory.getInstance().getDefaultProject()); - if (!courses.contains(Course.INVALID_COURSE)) { - refreshCoursesList(courses); - } - } - } - - private void refreshCoursesList(@NotNull final List courses) { - if (courses.isEmpty()) { - setError(CONNECTION_ERROR); - return; - } - myCoursesComboBox.removeAllItems(); - - addCoursesToCombobox(courses); - final Course selectedCourse = StudyUtils.getFirst(courses); - if (selectedCourse == null) return; - myGenerator.setSelectedCourse(selectedCourse); - - myGenerator.setCourses(courses); - myAvailableCourses = courses; - } - - private void addCoursesToCombobox(@NotNull List courses) { - for (Course courseInfo : courses) { - myCoursesComboBox.addItem(courseInfo); - } - } - - - /** - * Handles selecting course in combo box - * Sets selected course in combo box as selected in - * {@link StudyNewProjectPanel#myGenerator} - */ - private class CourseSelectedListener implements ActionListener { - @Override - public void actionPerformed(ActionEvent e) { - JComboBox cb = (JComboBox)e.getSource(); - Course selectedCourse = (Course)cb.getSelectedItem(); - if (selectedCourse == null || selectedCourse.equals(Course.INVALID_COURSE)) { - myAuthorLabel.setText(""); - myDescriptionPane.setText(""); - setError(INVALID_COURSE); - return; - } - updateInfoPanel(selectedCourse); - myCoursesComboBox.removeItem(Course.INVALID_COURSE); - myGenerator.setSelectedCourse(selectedCourse); - - setOK(); - if (selectedCourse.isAdaptive()) { - if(!myGenerator.isLoggedIn()) { - setError(LOGIN_TO_STEPIC_MESSAGE); - } - } - } - } - - private void setAuthors(Course selectedCourse) { - final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors()); - myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : ""); - } - - public JPanel getInfoPanel() { - return myInfoPanel; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyProgressBar.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyProgressBar.java deleted file mode 100644 index 6effcc2df65c..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyProgressBar.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.jetbrains.edu.learning.ui; - -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.ui.GraphicsConfig; -import com.intellij.ui.ColorUtil; -import com.intellij.ui.Gray; -import com.intellij.ui.JBColor; -import com.intellij.util.ui.GraphicsUtil; -import com.intellij.util.ui.UIUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.geom.Rectangle2D; - -public class StudyProgressBar extends JComponent implements DumbAware { - private static final Color SHADOW1 = new JBColor(Gray._190, JBColor.border()); - private static final Color SHADOW2 = Gray._105; - private static final int BRICK_WIDTH = 10; - private static final int BRICK_SPACE = 2; - private final int myHeight; - private final int myIndent; - private double myFraction = 0.0; - private static final Color myColor = JBColor.GREEN; - - public StudyProgressBar(double fraction, int height, int indent) { - myFraction = fraction; - myHeight = height; - myIndent = indent; - } - - private int getBricksToDraw(double fraction) { - int bricksTotal = (getWidth() - 8) / (BRICK_WIDTH + BRICK_SPACE); - return (int)(bricksTotal * fraction) + 1; - } - - protected void paintComponent(Graphics g) { - final GraphicsConfig config = GraphicsUtil.setupAAPainting(g); - Graphics2D g2 = (Graphics2D)g; - if (myFraction > 1) { - myFraction = 1; - } - - Dimension size = getSize(); - double width = size.getWidth() - 2*myIndent; - g2.setPaint(UIUtil.getTextFieldBackground()); - Rectangle2D rect = new Rectangle2D.Double(myIndent, 0, width, myHeight); - g2.fill(rect); - - g2.setPaint(new JBColor(SHADOW1, JBColor.border())); - rect.setRect(myIndent, 0, width, myHeight); - int arcWidth = 5; - int arcHeight = 5; - g2.drawRoundRect(myIndent, 0, (int)width, myHeight, arcWidth, arcHeight); - g2.setPaint(SHADOW2); - - int y_center = myHeight / 2; - int y_steps = myHeight / 2 - 3; - int alpha_step = y_steps > 0 ? (255 - 70) / y_steps : 255 - 70; - int x_offset = 4; - - g.setClip(4 + myIndent, 3, (int)width - 6, myHeight - 4); - - int bricksToDraw = myFraction == 0 ? 0 : getBricksToDraw(myFraction); - for (int i = 0; i < bricksToDraw; i++) { - g2.setPaint(myColor); - UIUtil.drawLine(g2, x_offset, y_center, x_offset + BRICK_WIDTH - 1, y_center); - for (int j = 0; j < y_steps; j++) { - Color color = ColorUtil.toAlpha(myColor, 255 - alpha_step * (j + 1)); - g2.setPaint(color); - UIUtil.drawLine(g2, x_offset, y_center - 1 - j, x_offset + BRICK_WIDTH - 1, y_center - 1 - j); - if (!(y_center % 2 != 0 && j == y_steps - 1)) { - UIUtil.drawLine(g2, x_offset, y_center + 1 + j, x_offset + BRICK_WIDTH - 1, y_center + 1 + j); - } - } - g2.setColor( - ColorUtil.toAlpha(myColor, 255 - alpha_step * (y_steps / 2 + 1))); - g2.drawRect(x_offset, y_center - y_steps, BRICK_WIDTH - 1, myHeight - 7); - x_offset += BRICK_WIDTH + BRICK_SPACE; - } - config.restore(); - } - - @Override - public Dimension getPreferredSize() { - Dimension dimension = super.getPreferredSize(); - dimension.height = myHeight + 10; - return dimension; - } - - @Override - public Dimension getMaximumSize() { - Dimension dimension = super.getMaximumSize(); - dimension.height = myHeight + 10; - return dimension; - } - - @Override - public Dimension getMinimumSize() { - Dimension dimension = super.getMinimumSize(); - dimension.height = myHeight + 10; - return dimension; - } - - public void setFraction(double fraction) { - myFraction = fraction; - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyStepicUserWidget.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyStepicUserWidget.java deleted file mode 100644 index 3aab044aec2b..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyStepicUserWidget.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.jetbrains.edu.learning.ui; - -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.ui.popup.JBPopup; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.wm.IconLikeCustomStatusBarWidget; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.ui.ClickListener; -import com.intellij.ui.HoverHyperlinkLabel; -import com.intellij.ui.HyperlinkAdapter; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.util.ui.JBUI; -import com.jetbrains.edu.learning.StudySettings; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicUser; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; -import java.awt.*; -import java.awt.event.MouseEvent; - -public class StudyStepicUserWidget implements IconLikeCustomStatusBarWidget { - public static final String ID = "StepicUser"; - private JLabel myComponent; - - - public StudyStepicUserWidget() { - StepicUser user = StudySettings.getInstance().getUser(); - Icon icon = user == null ? EducationalCoreIcons.StepikOff : EducationalCoreIcons.Stepik; - myComponent = new JLabel(icon); - - new ClickListener() { - @Override - public boolean onClick(@NotNull MouseEvent e, int clickCount) { - Point point = new Point(0, 0); - StepicUserComponent component = new StepicUserComponent(StudySettings.getInstance().getUser()); - final Dimension dimension = component.getPreferredSize(); - point = new Point(point.x - dimension.width, point.y - dimension.height); - component.showComponent(new RelativePoint(e.getComponent(), point)); - return true; - } - }.installOn(myComponent); - } - - @NotNull - @Override - public String ID() { - return ID; - } - - @Nullable - @Override - public WidgetPresentation getPresentation(@NotNull PlatformType type) { - return null; - } - - @Override - public void install(@NotNull StatusBar statusBar) { - } - - @Override - public void dispose() { - Disposer.dispose(this); - } - - public void update() { - StepicUser user = StudySettings.getInstance().getUser(); - Icon icon = user == null ? EducationalCoreIcons.StepikOff : EducationalCoreIcons.Stepik; - myComponent.setIcon(icon); - } - - @Override - public JComponent getComponent() { - return myComponent; - } - - - private static class StepicUserComponent extends JPanel { - - private JBPopup myPopup; - - public StepicUserComponent(@Nullable StepicUser user) { - super(); - BorderLayout layout = new BorderLayout(); - layout.setVgap(10); - setLayout(layout); - - if (user == null) { - createUserPanel("You're not logged in", "Log in to Stepik", createAuthorizeUserListener()); - } - else { - String firstName = user.getFirstName(); - String lastName = user.getLastName(); - String statusText; - if (firstName == null || lastName == null || firstName.isEmpty() || lastName.isEmpty()) { - statusText = "You're logged in"; - } - else { - statusText = "You're logged in as " + firstName + " " + lastName + ""; - } - createUserPanel(statusText, "Log out", createLogoutListener()); - } - } - - @NotNull - private HyperlinkAdapter createAuthorizeUserListener() { - return new HyperlinkAdapter() { - @Override - protected void hyperlinkActivated(HyperlinkEvent e) { - EduStepicConnector.doAuthorize(() -> StudyUtils.showOAuthDialog()); - myPopup.cancel(); - } - }; - } - - @NotNull - private HyperlinkAdapter createLogoutListener() { - return new HyperlinkAdapter() { - @Override - protected void hyperlinkActivated(HyperlinkEvent e) { - StudySettings.getInstance().setUser(null); - myPopup.cancel(); - } - }; - } - - private void createUserPanel(@NotNull String statusText, @NotNull String actionLabelText, @NotNull HyperlinkAdapter listener) { - int left_margin = 10; - int top_margin = 6; - int action_label_indent = 260; - - JPanel statusPanel = new JPanel(new BorderLayout()); - JLabel statusLabel = new JLabel(statusText); - statusPanel.add(Box.createVerticalStrut(top_margin), BorderLayout.PAGE_START); - statusPanel.add(Box.createHorizontalStrut(left_margin), BorderLayout.WEST); - statusPanel.add(statusLabel, BorderLayout.CENTER); - - JPanel actionPanel = new JPanel(new BorderLayout()); - HoverHyperlinkLabel actionLabel = new HoverHyperlinkLabel(actionLabelText); - actionLabel.addHyperlinkListener(listener); - actionPanel.add(Box.createHorizontalStrut(action_label_indent), BorderLayout.LINE_START); - actionPanel.add(actionLabel, BorderLayout.CENTER); - actionPanel.add(Box.createHorizontalStrut(left_margin), BorderLayout.EAST); - - JPanel mainPanel = new JPanel(); - BoxLayout layout = new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS); - mainPanel.setLayout(layout); - mainPanel.add(statusPanel); - mainPanel.add(Box.createVerticalStrut(6)); - mainPanel.add(actionPanel); - - add(mainPanel, BorderLayout.PAGE_START); - } - - @Override - public Dimension getPreferredSize() { - final Dimension preferredSize = super.getPreferredSize(); - final int width = JBUI.scale(300); - if (preferredSize.width < width){ - preferredSize.width = width; - } - return preferredSize; - } - - public void showComponent(RelativePoint point) { - myPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(this, this) - .setRequestFocus(true) - .setCancelOnOtherWindowOpen(true) - .setCancelOnClickOutside(true) - .setShowBorder(true) - .createPopup(); - - Disposer.register(ApplicationManager.getApplication(), new Disposable() { - @Override - public void dispose() { - Disposer.dispose(myPopup); - } - }); - - myPopup.show(point); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudySwingToolWindow.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudySwingToolWindow.java deleted file mode 100644 index be13a1b0f46d..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudySwingToolWindow.java +++ /dev/null @@ -1,69 +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.ui; - -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.colors.EditorColorsScheme; -import com.intellij.openapi.project.Project; -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; -import javax.swing.text.html.HTMLDocument; -import javax.swing.text.html.HTMLEditorKit; -import java.awt.*; - -public class StudySwingToolWindow extends StudyToolWindow { - private JTextPane myTaskTextPane; - - public StudySwingToolWindow() { - super(); - } - - @Override - public JComponent createTaskInfoPanel(Project project) { - myTaskTextPane = new JTextPane(); - final JBScrollPane scrollPane = new JBScrollPane(myTaskTextPane); - myTaskTextPane.setContentType(new HTMLEditorKit().getContentType()); - final EditorColorsScheme editorColorsScheme = EditorColorsManager.getInstance().getGlobalScheme(); - int fontSize = editorColorsScheme.getEditorFontSize(); - final String fontName = editorColorsScheme.getEditorFontName(); - final Font font = new Font(fontName, Font.PLAIN, fontSize); - String bodyRule = "body { font-family: " + font.getFamily() + "; " + - "font-size: " + font.getSize() + "pt; }" + - "pre {font-family: Courier; display: inline; ine-height: 50px; padding-top: 5px; padding-bottom: 5px; padding-left: 5px; background-color:" - + ColorUtil.toHex(ColorUtil.dimmer(UIUtil.getPanelBackground())) + ";}" + - "code {font-family: Courier; display: flex; float: left; background-color:" - + ColorUtil.toHex(ColorUtil.dimmer(UIUtil.getPanelBackground())) + ";}"; - ((HTMLDocument)myTaskTextPane.getDocument()).getStyleSheet().addRule(bodyRule); - myTaskTextPane.setEditable(false); - if (!UIUtil.isUnderDarcula()) { - myTaskTextPane.setBackground(EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground()); - } - myTaskTextPane.setBorder(new EmptyBorder(20, 20, 0, 10)); - myTaskTextPane.addHyperlinkListener(BrowserHyperlinkListener.INSTANCE); - return scrollPane; - } - - public void setText(@NotNull String text) { - myTaskTextPane.setText(text); - } -} - diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyTestContentPanel.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyTestContentPanel.java deleted file mode 100644 index 72500882ca3e..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyTestContentPanel.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.jetbrains.edu.learning.ui; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.ui.DocumentAdapter; -import com.intellij.ui.components.JBScrollPane; -import com.jetbrains.edu.learning.courseFormat.UserTest; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.text.BadLocationException; -import java.awt.*; - -public class StudyTestContentPanel extends JPanel { - private static final Dimension ourPreferredSize = new Dimension(300, 200); - private static final Font ourHeaderFont = new Font("Arial", Font.BOLD, 16); - private final JTextArea myInputArea = new JTextArea(); - private final JTextArea myOutputArea = new JTextArea(); - private static final Logger LOG = Logger.getInstance(StudyTestContentPanel.class.getName()); - - public StudyTestContentPanel(UserTest userTest) { - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - initContentLabel("input", myInputArea); - myInputArea.getDocument().addDocumentListener(new BufferUpdater(userTest.getInputBuffer())); - myOutputArea.getDocument().addDocumentListener(new BufferUpdater(userTest.getOutputBuffer())); - initContentLabel("output", myOutputArea); - setEditable(userTest.isEditable()); - } - - private void initContentLabel(@NotNull final String headerText, @NotNull final JTextArea contentArea) { - JLabel headerLabel = new JLabel(headerText); - headerLabel.setFont(ourHeaderFont); - this.add(headerLabel); - this.add(new JSeparator(SwingConstants.HORIZONTAL)); - JScrollPane scroll = new JBScrollPane(contentArea); - scroll.setPreferredSize(ourPreferredSize); - this.add(scroll); - } - - private void setEditable(boolean isEditable) { - myInputArea.setEditable(isEditable); - myOutputArea.setEditable(isEditable); - } - - public void addInputContent(final String content) { - myInputArea.setText(content); - } - - public void addOutputContent(final String content) { - myOutputArea.setText(content); - } - - private static class BufferUpdater extends DocumentAdapter { - private final StringBuilder myBuffer; - - private BufferUpdater(StringBuilder buffer) { - myBuffer = buffer; - } - - @Override - protected void textChanged(DocumentEvent e) { - myBuffer.delete(0, myBuffer.length()); - try { - myBuffer.append(e.getDocument().getText(0, e.getDocument().getLength())); - } - catch (BadLocationException exception) { - LOG.warn(exception); - } - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt deleted file mode 100644 index af416c718498..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.jetbrains.edu.learning.ui - -import com.intellij.execution.impl.ConsoleViewImpl -import com.intellij.openapi.editor.ex.EditorEx -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.intellij.openapi.wm.ToolWindowFactory -import com.jetbrains.edu.learning.StudyUtils - - -@JvmField val ID = "Test Results" -class StudyTestResultsToolWindowFactory : ToolWindowFactory { - override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { - val currentTask = StudyUtils.getCurrentTask(project) - if (currentTask != null) { - val consoleView = ConsoleViewImpl(project, true) - toolWindow.isToHideOnEmptyContent = true - - val contentManager = toolWindow.contentManager - val content = contentManager.factory.createContent(consoleView.component, null, false) - contentManager.addContent(content) - val editor = consoleView.editor - if (editor is EditorEx) { - editor.isRendererMode = true - } - - project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, getFileEditorManagerListener(toolWindow)) - } - } - - fun getFileEditorManagerListener(toolWindow: ToolWindow): FileEditorManagerListener { - return object : FileEditorManagerListener { - override fun fileClosed(source: FileEditorManager, file: VirtualFile) { - toolWindow.setAvailable(false, {}) - } - - override fun selectionChanged(event: FileEditorManagerEvent) { - toolWindow.setAvailable(false, {}) - } - } - } -} - - diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java deleted file mode 100644 index 3d1402bc27e2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java +++ /dev/null @@ -1,349 +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.ui; - -import com.intellij.ide.browsers.WebBrowserManager; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.EditorSettings; -import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.fileEditor.FileEditorManagerListener; -import com.intellij.openapi.fileTypes.FileTypeRegistry; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.SimpleToolWindowPanel; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.Pair; -import com.intellij.ui.JBCardLayout; -import com.intellij.ui.JBColor; -import com.intellij.ui.OnePixelSplitter; -import com.intellij.util.ui.JBUI; -import com.jetbrains.edu.coursecreator.actions.CCEditTaskTextAction; -import com.jetbrains.edu.coursecreator.settings.CCSettings; -import com.jetbrains.edu.learning.EduPluginConfigurator; -import com.jetbrains.edu.learning.StudyFileEditorManagerListener; -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.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import com.jetbrains.edu.learning.stepic.StepicAdaptiveReactionsPanel; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.util.List; - -public abstract class StudyToolWindow extends SimpleToolWindowPanel implements DataProvider, Disposable { - private static final Logger LOG = Logger.getInstance(StudyToolWindow.class); - private static final String TASK_INFO_ID = "taskInfo"; - public static final String EMPTY_TASK_TEXT = "Please, open any task to see task description"; - private static final String HELP_ID = "task.description"; - - private final JBCardLayout myCardLayout; - private final JPanel myContentPanel; - private final OnePixelSplitter mySplitPane; - private JLabel myStatisticLabel; - private StudyProgressBar myStudyProgressBar; - private EditorEx myEditor; - - public StudyToolWindow() { - super(true, true); - myCardLayout = new JBCardLayout(); - myContentPanel = new JPanel(myCardLayout); - mySplitPane = new OnePixelSplitter(myVertical = true); - } - - public void init(@NotNull final Project project, final boolean isToolwindow) { - final DefaultActionGroup group = getActionGroup(project); - setActionToolbar(group); - - final JPanel panel = new JPanel(new BorderLayout()); - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (isToolwindow && course != null && course.isAdaptive()) { - panel.add(new StepicAdaptiveReactionsPanel(project), BorderLayout.NORTH); - } - - JComponent taskInfoPanel = createTaskInfoPanel(project); - panel.add(taskInfoPanel, BorderLayout.CENTER); - - final JPanel courseProgress = createCourseProgress(project); - if (isToolwindow && course != null && !course.isAdaptive() && EduNames.STUDY.equals(course.getCourseMode())) { - panel.add(courseProgress, BorderLayout.SOUTH); - } - - myContentPanel.add(TASK_INFO_ID, panel); - mySplitPane.setFirstComponent(myContentPanel); - myCardLayout.show(myContentPanel, TASK_INFO_ID); - - setContent(mySplitPane); - - if (isToolwindow) { - project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new StudyFileEditorManagerListener(this, project)); - Task task = StudyUtils.getCurrentTask(project); - if (task == null) { - setEmptyText(project); - return; - } - String taskDescription = task.getTaskDescription(); - if (taskDescription == null) { - setEmptyText(project); - return; - } - setTaskText(taskDescription, project); - } - } - - public void setTopComponent(@NotNull final JComponent component) { - mySplitPane.setFirstComponent(component); - } - - public void setDefaultTopComponent() { - mySplitPane.setFirstComponent(myContentPanel); - } - - public void setActionToolbar(DefaultActionGroup group) { - JPanel toolbarPanel = createToolbarPanel(group); - setToolbar(toolbarPanel); - } - - public void dispose() { - } - - //used in checkiO plugin. - @SuppressWarnings("unused") - public void showPanelById(@NotNull final String panelId) { - myCardLayout.swipe(myContentPanel, panelId, JBCardLayout.SwipeDirection.AUTO); - } - - public void setBottomComponent(JComponent component) { - mySplitPane.setSecondComponent(component); - } - - //used in checkiO plugin. - @SuppressWarnings("unused") - public JComponent getBottomComponent() { - return mySplitPane.getSecondComponent(); - } - - //used in checkiO plugin. - @SuppressWarnings("unused") - public void setTopComponentPreferredSize(@NotNull final Dimension dimension) { - myContentPanel.setPreferredSize(dimension); - } - - //used in checkiO plugin. - @SuppressWarnings("unused") - public JPanel getContentPanel() { - return myContentPanel; - } - - - public abstract JComponent createTaskInfoPanel(Project project); - - public static JPanel createToolbarPanel(ActionGroup group) { - final ActionToolbar actionToolBar = ActionManager.getInstance().createActionToolbar("Study", group, true); - return JBUI.Panels.simplePanel(actionToolBar.getComponent()); - } - - public static DefaultActionGroup getActionGroup(@NotNull final Project project) { - DefaultActionGroup group = new DefaultActionGroup(); - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - LOG.warn("Course is null"); - return group; - } - EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); - if (configurator != null) { - group.addAll(configurator.getTaskDescriptionActionGroup()); - } - group.add(new CCEditTaskTextAction()); - return group; - } - - public void setTaskText(@NotNull String text, @NotNull Project project) { - if (StudyTaskManager.getInstance(project).isTurnEditingMode()) { - enterEditingMode(project); - StudyTaskManager.getInstance(project).setTurnEditingMode(false); - } - if (StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) { - enterEditingMode(project); - } - else { - setText(text); - } - } - - public abstract void setText(@NotNull String text); - - public void updateFonts(@NotNull Project project) { - - } - - public void setEmptyText(@NotNull Project project) { - if (StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) { - mySplitPane.setFirstComponent(myContentPanel); - StudyTaskManager.getInstance(project).setTurnEditingMode(true); - } - setTaskText(EMPTY_TASK_TEXT, project); - } - - public enum StudyToolWindowMode { - TEXT, EDITING - } - - - public void enterEditingMode(Project project) { - Task task = StudyUtils.getCurrentTask(project); - assert task != null; - String taskDescription = task.getTaskDescription(false); - final EditorFactory factory = EditorFactory.getInstance(); - Document document = factory.createDocument(taskDescription); - WebBrowserManager.getInstance().setShowBrowserHover(false); - String extension = CCSettings.getInstance().useHtmlAsDefaultTaskFormat() ? "html" : "md"; - myEditor = (EditorEx)factory.createEditor(document, project, FileTypeRegistry.getInstance().getFileTypeByExtension(extension), false); - Disposer.register(project, new Disposable() { - public void dispose() { - if (!myEditor.isDisposed()) { - factory.releaseEditor(myEditor); - } - } - }); - JComponent editorComponent = myEditor.getComponent(); - editorComponent.setBorder(new EmptyBorder(10, 20, 0, 10)); - editorComponent.setBackground(EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground()); - EditorSettings editorSettings = myEditor.getSettings(); - editorSettings.setLineMarkerAreaShown(false); - editorSettings.setLineNumbersShown(false); - editorSettings.setFoldingOutlineShown(false); - mySplitPane.setFirstComponent(editorComponent); - mySplitPane.repaint(); - - StudyTaskManager.getInstance(project).setToolWindowMode(StudyToolWindowMode.EDITING); - } - - - public void leaveEditingMode(Project project) { - Task task = StudyUtils.getCurrentTask(project); - assert task != null; - task.saveTaskText(myEditor.getDocument().getText()); - EditorFactory.getInstance().releaseEditor(myEditor); - WebBrowserManager.getInstance().setShowBrowserHover(true); - mySplitPane.setFirstComponent(myContentPanel); - StudyTaskManager.getInstance(project).setToolWindowMode(StudyToolWindowMode.TEXT); - StudyUtils.updateToolWindows(project); - } - - private JPanel createCourseProgress(@NotNull final Project project) { - JPanel contentPanel = new JPanel(); - contentPanel.setBackground(JBColor.WHITE); - contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.PAGE_AXIS)); - contentPanel.add(Box.createRigidArea(new Dimension(10, 0))); - contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); - myStudyProgressBar = new StudyProgressBar(0, 20, 10); - - myStatisticLabel = new JLabel("", SwingConstants.LEFT); - contentPanel.add(myStatisticLabel); - contentPanel.add(myStudyProgressBar); - - contentPanel.setPreferredSize(new Dimension(100, 60)); - contentPanel.setMinimumSize(new Dimension(300, 40)); - updateCourseProgress(project); - return contentPanel; - } - - public void updateCourseProgress(@NotNull final Project project) { - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course != null) { - List lessons = course.getLessons(); - - Pair progress = countProgressAsOneTaskWithSubtasks(lessons); - if (progress == null) { - progress = countProgressWithoutSubtasks(lessons); - } - - int taskSolved = progress.getFirst(); - int taskNum = progress.getSecond(); - String completedTasks = String.format("%d of %d tasks completed", taskSolved, taskNum); - double percent = (taskSolved * 100.0) / taskNum; - - myStatisticLabel.setText(completedTasks); - myStudyProgressBar.setFraction(percent / 100); - } - } - - /** - * Counts current progress for course which consists of only on task with subtasks - * In this case we count each subtasks as task - * @return Pair (number of solved tasks, number of tasks) or null if lessons can't be interpreted as one task with subtasks - */ - @Nullable - private static Pair countProgressAsOneTaskWithSubtasks(List lessons) { - if (lessons.size() == 1 && lessons.get(0).getTaskList().size() == 1) { - final Lesson lesson = lessons.get(0); - final Task task = lesson.getTaskList().get(0); - if (task instanceof TaskWithSubtasks) { - final int lastSubtaskIndex = ((TaskWithSubtasks)task).getLastSubtaskIndex(); - final int activeSubtaskIndex = ((TaskWithSubtasks)task).getActiveSubtaskIndex(); - int taskNum = lastSubtaskIndex + 1; - boolean isLastSubtaskSolved = activeSubtaskIndex == lastSubtaskIndex && task.getStatus() == StudyStatus.Solved; - return Pair.create(isLastSubtaskSolved ? taskNum : activeSubtaskIndex, taskNum); - } - } - return null; - } - - /** - * @return Pair (number of solved tasks, number of tasks) - */ - @NotNull - private static Pair countProgressWithoutSubtasks(List lessons) { - int taskNum = 0; - int taskSolved = 0; - for (Lesson lesson : lessons) { - taskNum += lesson.getTaskList().size(); - taskSolved += getSolvedTasks(lesson); - } - return Pair.create(taskSolved, taskNum); - } - - private static int getSolvedTasks(@NotNull final Lesson lesson) { - int solved = 0; - for (Task task : lesson.getTaskList()) { - if (task.getStatus() == StudyStatus.Solved) { - solved += 1; - } - } - return solved; - } - - @Nullable - @Override - public Object getData(String dataId) { - if (PlatformDataKeys.HELP_ID.is(dataId)) { - return HELP_ID; - } - return super.getData(dataId); - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java b/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java deleted file mode 100644 index a10d70e423a2..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jetbrains.edu.learning.ui; - -import com.intellij.openapi.project.DumbAware; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; -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.StudySettings; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.NotNull; - -public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware { - public static final String STUDY_TOOL_WINDOW = "Task Description"; - - - @Override - public void createToolWindowContent(@NotNull final Project project, @NotNull final ToolWindow toolWindow) { - toolWindow.setIcon(EducationalCoreIcons.TaskDescription); - final Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course != null) { - final StudyToolWindow studyToolWindow; - if (StudyUtils.hasJavaFx() && StudySettings.getInstance().shouldUseJavaFx()) { - studyToolWindow = new StudyJavaFxToolWindow(); - } - else { - studyToolWindow = new StudySwingToolWindow(); - } - studyToolWindow.init(project, true); - final ContentManager contentManager = toolWindow.getContentManager(); - final Content content = contentManager.getFactory().createContent(studyToolWindow, null, false); - contentManager.addContent(content); - Disposer.register(project, studyToolWindow); - } - } -} diff --git a/python/educational-core/src/icons/EducationalCoreIcons.java b/python/educational-core/src/icons/EducationalCoreIcons.java deleted file mode 100644 index aa3563e74c70..000000000000 --- a/python/educational-core/src/icons/EducationalCoreIcons.java +++ /dev/null @@ -1,35 +0,0 @@ -package icons; - -import com.intellij.openapi.util.IconLoader; - -import javax.swing.*; - -/** - * NOTE THIS FILE IS AUTO-GENERATED - * DO NOT EDIT IT BY HAND, run "Generate icon classes" configuration instead - */ -public class EducationalCoreIcons { - private static Icon load(String path) { - return IconLoader.getIcon(path, EducationalCoreIcons.class); - } - - public static final Icon CheckTask = load("/icons/com/jetbrains/edu/learning/CheckTask.png"); // 16x16 - public static final Icon Course = load("/icons/com/jetbrains/edu/learning/Course.png"); // 16x16 - public static final Icon CourseCreationProjectType = load("/icons/com/jetbrains/edu/learning/CourseCreationProjectType.png"); // 16x16 - public static final Icon CourseProgress = load("/icons/com/jetbrains/edu/learning/CourseProgress.png"); // 13x13 - public static final Icon EducationalProjectType = load("/icons/com/jetbrains/edu/learning/EducationalProjectType.png"); // 16x16 - public static final Icon InterpreterGear = load("/icons/com/jetbrains/edu/learning/interpreterGear.png"); // 16x16 - public static final Icon Lesson = load("/icons/com/jetbrains/edu/learning/Lesson.png"); // 16x16 - public static final Icon LessonCompl = load("/icons/com/jetbrains/edu/learning/LessonCompl.png"); // 16x16 - public static final Icon ResetTaskFile = load("/icons/com/jetbrains/edu/learning/ResetTaskFile.png"); // 16x16 - public static final Icon Sandbox = load("/icons/com/jetbrains/edu/learning/Sandbox.png"); // 16x16 - public static final Icon ShowHint = load("/icons/com/jetbrains/edu/learning/showHint.png"); // 16x16 - public static final Icon Stepik = load("/icons/com/jetbrains/edu/learning/Stepik.png"); // 16x16 - public static final Icon StepikOff = load("/icons/com/jetbrains/edu/learning/StepikOff.png"); // 16x16 - public static final Icon StepikRefresh = load("/icons/com/jetbrains/edu/learning/StepikRefresh.png"); // 16x16 - public static final Icon Task = load("/icons/com/jetbrains/edu/learning/Task.png"); // 16x16 - public static final Icon TaskCompl = load("/icons/com/jetbrains/edu/learning/TaskCompl.png"); // 16x16 - public static final Icon TaskDescription = load("/icons/com/jetbrains/edu/learning/TaskDescription.png"); // 13x13 - public static final Icon TaskProbl = load("/icons/com/jetbrains/edu/learning/TaskProbl.png"); // 16x16 - public static final Icon WatchInput = load("/icons/com/jetbrains/edu/learning/WatchInput.png"); // 24x24 -} diff --git a/python/educational-core/testData/actions/addPlaceholder/deletePlaceholder_after.txt b/python/educational-core/testData/actions/addPlaceholder/deletePlaceholder_after.txt deleted file mode 100644 index 56b857f4b334..000000000000 --- a/python/educational-core/testData/actions/addPlaceholder/deletePlaceholder_after.txt +++ /dev/null @@ -1 +0,0 @@ -we're going to delete this placeholder \ No newline at end of file diff --git a/python/educational-core/testData/actions/addPlaceholder/deletePlaceholder_before.txt b/python/educational-core/testData/actions/addPlaceholder/deletePlaceholder_before.txt deleted file mode 100644 index 4c54702d14ad..000000000000 --- a/python/educational-core/testData/actions/addPlaceholder/deletePlaceholder_before.txt +++ /dev/null @@ -1 +0,0 @@ -we're going to delete this placeholder \ No newline at end of file diff --git a/python/educational-core/testData/actions/addPlaceholder/onePlaceholder_after.txt b/python/educational-core/testData/actions/addPlaceholder/onePlaceholder_after.txt deleted file mode 100644 index ae22d673d535..000000000000 --- a/python/educational-core/testData/actions/addPlaceholder/onePlaceholder_after.txt +++ /dev/null @@ -1 +0,0 @@ -here will be added one placeholder \ No newline at end of file diff --git a/python/educational-core/testData/actions/addPlaceholder/onePlaceholder_before.txt b/python/educational-core/testData/actions/addPlaceholder/onePlaceholder_before.txt deleted file mode 100644 index ba43c8302b9a..000000000000 --- a/python/educational-core/testData/actions/addPlaceholder/onePlaceholder_before.txt +++ /dev/null @@ -1 +0,0 @@ -here will be added one placeholder \ No newline at end of file diff --git a/python/educational-core/testData/actions/addPlaceholder/placeholderIntersection.txt b/python/educational-core/testData/actions/addPlaceholder/placeholderIntersection.txt deleted file mode 100644 index c5c6689f9547..000000000000 --- a/python/educational-core/testData/actions/addPlaceholder/placeholderIntersection.txt +++ /dev/null @@ -1 +0,0 @@ -type here \ No newline at end of file diff --git a/python/educational-core/testData/actions/addPlaceholder/withoutSelection_after.txt b/python/educational-core/testData/actions/addPlaceholder/withoutSelection_after.txt deleted file mode 100644 index fd68bcc23753..000000000000 --- a/python/educational-core/testData/actions/addPlaceholder/withoutSelection_after.txt +++ /dev/null @@ -1,3 +0,0 @@ -def f(): - pass - type here \ No newline at end of file diff --git a/python/educational-core/testData/actions/addPlaceholder/withoutSelection_before.txt b/python/educational-core/testData/actions/addPlaceholder/withoutSelection_before.txt deleted file mode 100644 index 186cd19e9bec..000000000000 --- a/python/educational-core/testData/actions/addPlaceholder/withoutSelection_before.txt +++ /dev/null @@ -1,3 +0,0 @@ -def f(): - pass - \ No newline at end of file diff --git a/python/educational-core/testData/actions/preview/noplaceholders.txt b/python/educational-core/testData/actions/preview/noplaceholders.txt deleted file mode 100644 index ad224057c029..000000000000 --- a/python/educational-core/testData/actions/preview/noplaceholders.txt +++ /dev/null @@ -1 +0,0 @@ -no placeholders \ No newline at end of file diff --git a/python/educational-core/testData/actions/preview/several_after.txt b/python/educational-core/testData/actions/preview/several_after.txt deleted file mode 100644 index 942275772774..000000000000 --- a/python/educational-core/testData/actions/preview/several_after.txt +++ /dev/null @@ -1 +0,0 @@ -print("veryverylongtextveryverylongtextveryverylongtextveryverylongtext + test = 2") \ No newline at end of file diff --git a/python/educational-core/testData/actions/preview/several_before.txt b/python/educational-core/testData/actions/preview/several_before.txt deleted file mode 100644 index 652621a9d5c2..000000000000 --- a/python/educational-core/testData/actions/preview/several_before.txt +++ /dev/null @@ -1 +0,0 @@ -print("1 + 1 = 2") \ No newline at end of file diff --git a/python/educational-core/testData/actions/preview/test_after.txt b/python/educational-core/testData/actions/preview/test_after.txt deleted file mode 100644 index 9a1e3731db86..000000000000 --- a/python/educational-core/testData/actions/preview/test_after.txt +++ /dev/null @@ -1 +0,0 @@ -type here \ No newline at end of file diff --git a/python/educational-core/testData/actions/preview/test_before.txt b/python/educational-core/testData/actions/preview/test_before.txt deleted file mode 100644 index 3012cb8bfa2b..000000000000 --- a/python/educational-core/testData/actions/preview/test_before.txt +++ /dev/null @@ -1 +0,0 @@ -two \ No newline at end of file diff --git a/python/educational-core/testData/actions/taskFileActions/nonTaskFile.txt b/python/educational-core/testData/actions/taskFileActions/nonTaskFile.txt deleted file mode 100644 index 57f41b7baafc..000000000000 --- a/python/educational-core/testData/actions/taskFileActions/nonTaskFile.txt +++ /dev/null @@ -1 +0,0 @@ -non task file \ No newline at end of file diff --git a/python/educational-core/testData/actions/taskFileActions/taskFile.txt b/python/educational-core/testData/actions/taskFileActions/taskFile.txt deleted file mode 100644 index 03f277cc111c..000000000000 --- a/python/educational-core/testData/actions/taskFileActions/taskFile.txt +++ /dev/null @@ -1 +0,0 @@ -test task file with placeholder \ No newline at end of file diff --git a/python/educational-core/testData/migration/adaptive45.after.xml b/python/educational-core/testData/migration/adaptive45.after.xml deleted file mode 100644 index 0355503300c2..000000000000 --- a/python/educational-core/testData/migration/adaptive45.after.xml +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/python/educational-core/testData/migration/adaptive45.xml b/python/educational-core/testData/migration/adaptive45.xml deleted file mode 100644 index e18194f223fc..000000000000 --- a/python/educational-core/testData/migration/adaptive45.xml +++ /dev/null @@ -1,175 +0,0 @@ - - - - - - - diff --git a/python/educational-core/testData/migration/fromThirdToForth.after.xml b/python/educational-core/testData/migration/fromThirdToForth.after.xml deleted file mode 100644 index b0c11ff543b8..000000000000 --- a/python/educational-core/testData/migration/fromThirdToForth.after.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/python/educational-core/testData/migration/fromThirdToForth.xml b/python/educational-core/testData/migration/fromThirdToForth.xml deleted file mode 100644 index b347959a91f0..000000000000 --- a/python/educational-core/testData/migration/fromThirdToForth.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/python/educational-core/testData/migration/subtasks45.after.xml b/python/educational-core/testData/migration/subtasks45.after.xml deleted file mode 100644 index e11207ce1d0c..000000000000 --- a/python/educational-core/testData/migration/subtasks45.after.xml +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/python/educational-core/testData/migration/subtasks45.xml b/python/educational-core/testData/migration/subtasks45.xml deleted file mode 100644 index b8a4ca5b482d..000000000000 --- a/python/educational-core/testData/migration/subtasks45.xml +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/python/educational-core/testData/migration/theory351To4.after.xml b/python/educational-core/testData/migration/theory351To4.after.xml deleted file mode 100644 index bec1df0148de..000000000000 --- a/python/educational-core/testData/migration/theory351To4.after.xml +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/python/educational-core/testData/migration/theory351To4.xml b/python/educational-core/testData/migration/theory351To4.xml deleted file mode 100644 index 205453ac34d0..000000000000 --- a/python/educational-core/testData/migration/theory351To4.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/python/educational-core/testData/migration/theory35To4.after.xml b/python/educational-core/testData/migration/theory35To4.after.xml deleted file mode 100644 index 2ff33d1dcf62..000000000000 --- a/python/educational-core/testData/migration/theory35To4.after.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/python/educational-core/testData/migration/theory35To4.xml b/python/educational-core/testData/migration/theory35To4.xml deleted file mode 100644 index 08e98366e4aa..000000000000 --- a/python/educational-core/testData/migration/theory35To4.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - diff --git a/python/educational-core/testData/stepic/1.json b/python/educational-core/testData/stepic/1.json deleted file mode 100644 index 9a40915cf279..000000000000 --- a/python/educational-core/testData/stepic/1.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "meta": { - "page": 1, - "has_next": false, - "has_previous": false - }, - "steps": [ - { - "id": 98626, - "lesson": 13416, - "position": 1, - "status": "ready", - "block": { - "name": "pycharm", - "text": "\nTraditionally the first program you write in any programming language is \"Hello World!\".\n

\nIntroduce yourself to the World.\n
\n", - "video": null, - "animation": null, - "options": { - "test": [ - { - "text": "from test_helper import run_common_tests, failed, passed, get_answer_placeholders\n\n\ndef test_ASCII():\n windows = get_answer_placeholders()\n for window in windows:\n all_ascii = all(ord(c) < 128 for c in window)\n if not all_ascii:\n failed(\"Please use only English characters this time.\")\n return\n passed()\n\n\ndef test_is_alpha():\n window = get_answer_placeholders()[0]\n is_multiline = window.find(\"\\n\")\n if is_multiline != -1:\n window = window[:is_multiline-1]\n splitted = window.split()\n for s in splitted:\n if not s.isalpha():\n failed(\"Please use only English characters this time.\")\n return\n\n passed()\n\n\nif __name__ == '__main__':\n test_ASCII()\n run_common_tests(\"You should enter your name\")\n test_is_alpha()\n\n\n", - "name": "tests.py" - } - ], - "files": [ - { - "placeholders": [ - { - "hint": "[\"Type your name here.\"]", - "start": 32, - "length": 14, - "possible_answer": "Liana", - "line": -1 - } - ], - "text": "print(\"Hello, world! My name is type your name\")\n", - "name": "hello_world.py" - } - ], - "title": "Our first program" - }, - "subtitle_files": [] - }, - "actions": { - "submit": "#" - }, - "progress": "77-98626", - "subscriptions": [ - "31-77-98626", - "30-77-98626" - ], - "instruction": null, - "session": null, - "instruction_type": null, - "viewed_by": 46, - "passed_by": 2433, - "correct_ratio": 0.7805243445692884, - "worth": null, - "is_solutions_unlocked": false, - "solutions_unlocked_attempts": 3, - "has_submissions_restrictions": false, - "max_submissions_count": 3, - "create_date": "2016-04-11T15:25:39Z", - "update_date": "2016-08-03T11:05:13Z", - "discussions_count": 0, - "discussion_proxy": "77-98626-1", - "discussion_threads": [ - "77-98626-1" - ] - } - ] -} \ No newline at end of file diff --git a/python/educational-core/testData/stepic/2.json b/python/educational-core/testData/stepic/2.json deleted file mode 100644 index ad4485b852b8..000000000000 --- a/python/educational-core/testData/stepic/2.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "meta": { - "page": 1, - "has_next": false, - "has_previous": false - }, - "steps": [ - { - "id": 98626, - "lesson": 13416, - "position": 1, - "status": "ready", - "block": { - "name": "pycharm", - "text": "\nTraditionally the first program you write in any programming language is \"Hello World!\".\n

\nIntroduce yourself to the World.\n
\n", - "video": null, - "animation": null, - "options": { - "test": [ - { - "text": "from test_helper import run_common_tests, failed, passed, get_answer_placeholders\n\n\ndef test_ASCII():\n windows = get_answer_placeholders()\n for window in windows:\n all_ascii = all(ord(c) < 128 for c in window)\n if not all_ascii:\n failed(\"Please use only English characters this time.\")\n return\n passed()\n\n\ndef test_is_alpha():\n window = get_answer_placeholders()[0]\n is_multiline = window.find(\"\\n\")\n if is_multiline != -1:\n window = window[:is_multiline-1]\n splitted = window.split()\n for s in splitted:\n if not s.isalpha():\n failed(\"Please use only English characters this time.\")\n return\n\n passed()\n\n\nif __name__ == '__main__':\n test_ASCII()\n run_common_tests(\"You should enter your name\")\n test_is_alpha()\n\n\n", - "name": "tests.py" - } - ], - "files": [ - { - "placeholders": [ - { - "hint": "[\"Type your name here.\"]", - "offset": 32, - "length": 14, - "subtask_infos": [{ - "index" : 0, - "hints": ["Type your name here."], - "possible_answer": "Liana" - } - ] - } - ], - "text": "print(\"Hello, world! My name is type your name\")\n", - "name": "hello_world.py" - } - ], - "title": "Our first program", - "format_version": 2, - "last_subtask_index": 0 - }, - "subtitle_files": [] - }, - "actions": { - "submit": "#" - }, - "progress": "77-98626", - "subscriptions": [ - "31-77-98626", - "30-77-98626" - ], - "instruction": null, - "session": null, - "instruction_type": null, - "viewed_by": 46, - "passed_by": 2433, - "correct_ratio": 0.7805243445692884, - "worth": null, - "is_solutions_unlocked": false, - "solutions_unlocked_attempts": 3, - "has_submissions_restrictions": false, - "max_submissions_count": 3, - "create_date": "2016-04-11T15:25:39Z", - "update_date": "2016-08-03T11:05:13Z", - "discussions_count": 0, - "discussion_proxy": "77-98626-1", - "discussion_threads": [ - "77-98626-1" - ] - } - ] -} \ No newline at end of file diff --git a/python/educational-core/testData/stepic/3.json b/python/educational-core/testData/stepic/3.json deleted file mode 100644 index 147620c7efef..000000000000 --- a/python/educational-core/testData/stepic/3.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "meta": { - "page": 1, - "has_next": false, - "has_previous": false - }, - "steps": [ - { - "id": 98626, - "lesson": 13416, - "position": 1, - "status": "ready", - "block": { - "name": "pycharm", - "text": "\nTraditionally the first program you write in any programming language is \"Hello World!\".\n

\nIntroduce yourself to the World.\n
\n", - "video": null, - "animation": null, - "options": { - "test": [ - { - "text": "from test_helper import run_common_tests, failed, passed, get_answer_placeholders\n\n\ndef test_ASCII():\n windows = get_answer_placeholders()\n for window in windows:\n all_ascii = all(ord(c) < 128 for c in window)\n if not all_ascii:\n failed(\"Please use only English characters this time.\")\n return\n passed()\n\n\ndef test_is_alpha():\n window = get_answer_placeholders()[0]\n is_multiline = window.find(\"\\n\")\n if is_multiline != -1:\n window = window[:is_multiline-1]\n splitted = window.split()\n for s in splitted:\n if not s.isalpha():\n failed(\"Please use only English characters this time.\")\n return\n\n passed()\n\n\nif __name__ == '__main__':\n test_ASCII()\n run_common_tests(\"You should enter your name\")\n test_is_alpha()\n\n\n", - "name": "tests.py" - } - ], - "files": [ - { - "placeholders": [ - { - "hint": "[\"Type your name here.\"]", - "offset": 32, - "length": 14, - "subtask_infos": [ - { - "index": 0, - "hints": [ - "Type your name here." - ], - "possible_answer": "Liana" - }, - { - "index": 1, - "hints": [], - "possible_answer": "miss X" - } - ] - } - ], - "text": "print(\"Hello, world! My name is type your name\")\n", - "name": "hello_world.py" - } - ], - "title": "Our first program", - "format_version": 2, - "last_subtask_index": 1 - }, - "subtitle_files": [] - }, - "actions": { - "submit": "#" - }, - "progress": "77-98626", - "subscriptions": [ - "31-77-98626", - "30-77-98626" - ], - "instruction": null, - "session": null, - "instruction_type": null, - "viewed_by": 46, - "passed_by": 2433, - "correct_ratio": 0.7805243445692884, - "worth": null, - "is_solutions_unlocked": false, - "solutions_unlocked_attempts": 3, - "has_submissions_restrictions": false, - "max_submissions_count": 3, - "create_date": "2016-04-11T15:25:39Z", - "update_date": "2016-08-03T11:05:13Z", - "discussions_count": 0, - "discussion_proxy": "77-98626-1", - "discussion_threads": [ - "77-98626-1" - ] - } - ] -} \ No newline at end of file diff --git a/python/educational-core/testData/stepic/courses.json b/python/educational-core/testData/stepic/courses.json deleted file mode 100644 index e4ab45223869..000000000000 --- a/python/educational-core/testData/stepic/courses.json +++ /dev/null @@ -1,330 +0,0 @@ -{ - "meta": { - "page": 1, - "has_next": false, - "has_previous": false - }, - "courses": [ - { - "id": 568, - "summary": "Adaptive problem set to learn Python. \r\nJoin the course and you can try out the first prototype of the adaptive engine! ", - "workload": "", - "cover": "/media/covers/py-en.png", - "intro": "", - "course_format": "Adaptive course with only programming assignments for Python", - "target_audience": "", - "certificate_footer": null, - "certificate_cover_org": null, - "is_certificate_auto_issued": false, - "certificate_regular_threshold": null, - "certificate_distinction_threshold": null, - "instructors": [], - "certificate": "", - "requirements": "

\r\n\r\nThe desire to test your knowledge of the Python language. 

", - "description": "

The course consists of few hundreds of programming assignments for Python, ranging from basics up to complex topics.

\n

\n\nRight now Stepik.org is developing an adaptive learning engine which chooses content for each learner individually \u2013 by his/her level and knowledge gaps. In this course you can try out the first prototype of this engine\n\n

Learn more \u2014 in the https://stepic.zendesk.com/hc/en-us/articles/210007905\ufeff.

", - "sections": [ - 1785, - 1786 - ], - "total_units": 347, - "enrollment": null, - "is_favorite": false, - "actions": {}, - "progress": null, - "certificate_link": null, - "certificate_regular_link": null, - "certificate_distinction_link": null, - "schedule_link": null, - "schedule_long_link": null, - "first_deadline": null, - "last_deadline": null, - "subscriptions": [ - "31-78-568", - "30-78-568" - ], - "announcements": [], - "is_contest": false, - "is_adaptive": true, - "is_idea_compatible": true, - "last_step": "78-568", - "intro_video": null, - "social_providers": [], - "authors": [ - 17813950 - ], - "tags": [ - 147 - ], - "has_tutors": false, - "is_enabled": true, - "review_summary": 295, - "owner": 17813950, - "language": "en", - "is_featured": true, - "is_public": true, - "title": "Adaptive Python", - "slug": "Adaptive-Python-568", - "begin_date": null, - "end_date": null, - "soft_deadline": null, - "hard_deadline": null, - "grading_policy": "halved", - "begin_date_source": null, - "end_date_source": null, - "soft_deadline_source": null, - "hard_deadline_source": null, - "grading_policy_source": "halved", - "is_active": true, - "create_date": "2016-03-17T13:57:04Z", - "update_date": "2016-10-20T12:25:01Z", - "learners_group": null, - "testers_group": null, - "moderators_group": null, - "teachers_group": null, - "admins_group": null, - "discussions_count": 0, - "discussion_proxy": null, - "discussion_threads": [] - }, - { - "id": 238, - "summary": "Introduction course to Python", - "workload": "", - "cover": null, - "intro": "", - "course_format": "pycharm Python", - "target_audience": "", - "certificate_footer": null, - "certificate_cover_org": null, - "is_certificate_auto_issued": false, - "certificate_regular_threshold": 0, - "certificate_distinction_threshold": 0, - "instructors": [ - 1794841 - ], - "certificate": "", - "requirements": "", - "description": "", - "sections": [ - 662 - ], - "total_units": 10, - "enrollment": null, - "is_favorite": false, - "actions": {}, - "progress": null, - "certificate_link": null, - "certificate_regular_link": null, - "certificate_distinction_link": null, - "schedule_link": null, - "schedule_long_link": null, - "first_deadline": null, - "last_deadline": null, - "subscriptions": [ - "31-78-238", - "30-78-238" - ], - "announcements": [], - "is_contest": false, - "is_adaptive": false, - "is_idea_compatible": true, - "last_step": "78-238", - "intro_video": null, - "social_providers": [], - "authors": [ - 625832 - ], - "tags": [], - "has_tutors": false, - "is_enabled": true, - "review_summary": 135, - "owner": 625832, - "language": "en", - "is_featured": false, - "is_public": true, - "title": "Introduction to Python", - "slug": "Introduction-to-Python-238", - "begin_date": null, - "end_date": null, - "soft_deadline": null, - "hard_deadline": null, - "grading_policy": "halved", - "begin_date_source": null, - "end_date_source": null, - "soft_deadline_source": null, - "hard_deadline_source": null, - "grading_policy_source": "halved", - "is_active": true, - "create_date": "2015-08-25T08:57:04Z", - "update_date": "2016-08-10T13:14:02Z", - "learners_group": null, - "testers_group": null, - "moderators_group": null, - "teachers_group": null, - "admins_group": null, - "discussions_count": 0, - "discussion_proxy": null, - "discussion_threads": [] - }, - { - "id": 163, - "summary": "Test PyCharm course in 2 format", - "workload": "", - "cover": null, - "intro": "", - "course_format": "pycharm2 Python", - "target_audience": "", - "certificate_footer": null, - "certificate_cover_org": null, - "is_certificate_auto_issued": false, - "certificate_regular_threshold": 0, - "certificate_distinction_threshold": 0, - "instructors": [ - 1777801 - ], - "certificate": "", - "requirements": "", - "description": "", - "sections": [ - 460 - ], - "total_units": 6, - "enrollment": null, - "is_favorite": false, - "actions": {}, - "progress": null, - "certificate_link": null, - "certificate_regular_link": null, - "certificate_distinction_link": null, - "schedule_link": null, - "schedule_long_link": null, - "first_deadline": null, - "last_deadline": null, - "subscriptions": [ - "31-78-163", - "30-78-163" - ], - "announcements": [], - "is_contest": false, - "is_adaptive": false, - "is_idea_compatible": true, - "last_step": "78-163", - "intro_video": null, - "social_providers": [], - "authors": [ - 625832 - ], - "tags": [], - "has_tutors": false, - "is_enabled": true, - "review_summary": 113, - "owner": 625832, - "language": "en", - "is_featured": false, - "is_public": true, - "title": "format2", - "slug": "Introduction-to-Classic-Ciphers-163", - "begin_date": null, - "end_date": null, - "soft_deadline": null, - "hard_deadline": null, - "grading_policy": "halved", - "begin_date_source": null, - "end_date_source": null, - "soft_deadline_source": null, - "hard_deadline_source": null, - "grading_policy_source": "halved", - "is_active": true, - "create_date": "2015-07-01T17:07:48Z", - "update_date": "2016-08-05T09:26:50Z", - "learners_group": null, - "testers_group": null, - "moderators_group": null, - "teachers_group": null, - "admins_group": null, - "discussions_count": 0, - "discussion_proxy": null, - "discussion_threads": [] - }, - { - "id": 162, - "summary": "Test Pycharm future format", - "workload": "", - "cover": null, - "intro": "", - "course_format": "pycharm3 Python", - "target_audience": "", - "certificate_footer": null, - "certificate_cover_org": null, - "is_certificate_auto_issued": false, - "certificate_regular_threshold": 0, - "certificate_distinction_threshold": 0, - "instructors": [], - "certificate": "", - "requirements": "", - "description": "", - "sections": [ - 459, - 2510 - ], - "total_units": 9, - "enrollment": null, - "is_favorite": false, - "actions": {}, - "progress": null, - "certificate_link": null, - "certificate_regular_link": null, - "certificate_distinction_link": null, - "schedule_link": null, - "schedule_long_link": null, - "first_deadline": null, - "last_deadline": null, - "subscriptions": [ - "31-78-162", - "30-78-162" - ], - "announcements": [], - "is_contest": false, - "is_adaptive": false, - "is_idea_compatible": true, - "last_step": "78-162", - "intro_video": null, - "social_providers": [], - "authors": [ - 625832 - ], - "tags": [], - "has_tutors": false, - "is_enabled": true, - "review_summary": 112, - "owner": 625832, - "language": "en", - "is_featured": false, - "is_public": true, - "title": "format3", - "slug": "Logging-in-Python-162", - "begin_date": null, - "end_date": null, - "soft_deadline": null, - "hard_deadline": null, - "grading_policy": "halved", - "begin_date_source": null, - "end_date_source": null, - "soft_deadline_source": null, - "hard_deadline_source": null, - "grading_policy_source": "halved", - "is_active": true, - "create_date": "2015-07-01T16:21:09Z", - "update_date": "2016-09-07T15:22:29Z", - "learners_group": null, - "testers_group": null, - "moderators_group": null, - "teachers_group": null, - "admins_group": null, - "discussions_count": 0, - "discussion_proxy": null, - "discussion_threads": [] - } - ], - "enrollments": [] -} \ No newline at end of file diff --git a/python/educational-core/testData/stepic/placeholder.json b/python/educational-core/testData/stepic/placeholder.json deleted file mode 100644 index 0c4799bdd165..000000000000 --- a/python/educational-core/testData/stepic/placeholder.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "offset": 1, - "length": 10, - "subtask_infos": [ - { - "hints": [ - "hint 1", - "hint 2" - ], - "possible_answer": "answer1", - "placeholder_text": "type here", - "has_frame": true, - "need_insert_text": true, - "index": 0 - }, - { - "hints": [ - "hint 11", - "hint 22" - ], - "possible_answer": "answer2", - "placeholder_text": "type here1", - "has_frame": true, - "need_insert_text": true, - "index": 1 - } - ] -} \ No newline at end of file diff --git a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java b/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java deleted file mode 100644 index 95be2c1581d0..000000000000 --- a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestCase.java +++ /dev/null @@ -1,211 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.Result; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.markup.MarkupModel; -import com.intellij.openapi.editor.markup.RangeHighlighter; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.testFramework.EditorTestUtil; -import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.*; -import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.junit.ComparisonFailure; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public abstract class CCTestCase extends LightPlatformCodeInsightFixtureTestCase { - private static final Logger LOG = Logger.getInstance(CCTestCase.class); - - @Nullable - public static RangeHighlighter getHighlighter(MarkupModel model, AnswerPlaceholder placeholder) { - for (RangeHighlighter highlighter : model.getAllHighlighters()) { - int endOffset = placeholder.getOffset() + placeholder.getRealLength(); - if (highlighter.getStartOffset() == placeholder.getOffset() && highlighter.getEndOffset() == endOffset) { - return highlighter; - } - } - return null; - } - - protected static void checkHighlighters(TaskFile taskFile, MarkupModel markupModel) { - for (AnswerPlaceholder answerPlaceholder : taskFile.getActivePlaceholders()) { - if (getHighlighter(markupModel, answerPlaceholder) == null) { - throw new AssertionError("No highlighter for placeholder: " + CCTestsUtil.getPlaceholderPresentation(answerPlaceholder)); - } - } - } - - public void checkByFile(TaskFile taskFile, String fileName, boolean useLength) { - Pair> placeholders = getPlaceholders(fileName, useLength, true); - String message = "Placeholders don't match"; - if (taskFile.getActivePlaceholders().size() != placeholders.second.size()) { - throw new ComparisonFailure(message, - CCTestsUtil.getPlaceholdersPresentation(taskFile.getActivePlaceholders()), - CCTestsUtil.getPlaceholdersPresentation(placeholders.second)); - } - for (AnswerPlaceholder answerPlaceholder : placeholders.getSecond()) { - AnswerPlaceholder placeholder = taskFile.getAnswerPlaceholder(answerPlaceholder.getOffset()); - if (!CCTestsUtil.comparePlaceholders(placeholder, answerPlaceholder)) { - throw new ComparisonFailure(message, - CCTestsUtil.getPlaceholdersPresentation(taskFile.getActivePlaceholders()), - CCTestsUtil.getPlaceholdersPresentation(placeholders.second)); - } - } - } - - @Override - protected String getBasePath() { - return new File("testData").getAbsolutePath().replace(File.separatorChar, '/'); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - Course course = new Course(); - course.setName("test course"); - StudyTaskManager.getInstance(getProject()).setCourse(course); - - Lesson lesson = new Lesson(); - lesson.setName("lesson1"); - Task task = new PyCharmTask(); - task.setName("task1"); - task.setIndex(1); - lesson.addTask(task); - lesson.setIndex(1); - course.addLesson(lesson); - course.setCourseMode(CCUtils.COURSE_MODE); - course.initCourse(false); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - VirtualFile lesson1 = myFixture.getProject().getBaseDir().createChildDirectory(this, "lesson1"); - lesson1.createChildDirectory(this, "task1"); - } - catch (IOException e) { - //ignore - } - } - }); - } - - protected VirtualFile copyFileToTask(String name) { - return myFixture.copyFileToProject(name, "lesson1/task1/" + name); - } - - protected VirtualFile configureByTaskFile(String name) { - Task task = StudyTaskManager.getInstance(getProject()).getCourse().getLessons().get(0).getTaskList().get(0); - TaskFile taskFile = new TaskFile(); - taskFile.setTask(task); - task.getTaskFiles().put(name, taskFile); - VirtualFile file = copyFileToTask(name); - - taskFile.name = name; - myFixture.configureFromExistingVirtualFile(file); - Document document = FileDocumentManager.getInstance().getDocument(file); - for (AnswerPlaceholder placeholder : getPlaceholders(document, false)) { - taskFile.addAnswerPlaceholder(placeholder); - } - taskFile.sortAnswerPlaceholders(); - StudyUtils.drawAllAnswerPlaceholders(myFixture.getEditor(), taskFile); - return file; - } - - private static List getPlaceholders(Document document, boolean useLength) { - final List placeholders = new ArrayList<>(); - new WriteCommandAction(null) { - @Override - protected void run(@NotNull Result result) { - final String openingTagRx = ""; - final String closingTagRx = ""; - CharSequence text = document.getCharsSequence(); - final Matcher openingMatcher = Pattern.compile(openingTagRx).matcher(text); - final Matcher closingMatcher = Pattern.compile(closingTagRx).matcher(text); - int pos = 0; - while (openingMatcher.find(pos)) { - AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder(); - AnswerPlaceholderSubtaskInfo subtaskInfo = new AnswerPlaceholderSubtaskInfo(); - answerPlaceholder.getSubtaskInfos().put(0, subtaskInfo); - answerPlaceholder.setUseLength(useLength); - String taskText = openingMatcher.group(2); - if (taskText != null) { - answerPlaceholder.setTaskText(taskText); - answerPlaceholder.setLength(taskText.length()); - } - String possibleAnswer = openingMatcher.group(4); - if (possibleAnswer != null) { - answerPlaceholder.setPossibleAnswer(possibleAnswer); - } - String hint = openingMatcher.group(6); - if (hint != null) { - answerPlaceholder.setHints(Collections.singletonList(hint)); - } - answerPlaceholder.setOffset(openingMatcher.start()); - if (!closingMatcher.find(openingMatcher.end())) { - LOG.error("No matching closing tag found"); - } - if (useLength) { - answerPlaceholder.setLength(closingMatcher.start() - openingMatcher.end()); - } else { - if (possibleAnswer == null) { - answerPlaceholder.setPossibleAnswer(document.getText(TextRange.create(openingMatcher.end(), closingMatcher.start()))); - } - } - document.deleteString(closingMatcher.start(), closingMatcher.end()); - document.deleteString(openingMatcher.start(), openingMatcher.end()); - FileDocumentManager.getInstance().saveDocument(document); - placeholders.add(answerPlaceholder); - pos = answerPlaceholder.getOffset() + answerPlaceholder.getRealLength(); - } - } - }.execute(); - return placeholders; - } - - public Pair> getPlaceholders(String name) { - return getPlaceholders(name, true, false); - } - - public Pair> getPlaceholders(String name, boolean useLength, boolean removeMarkers) { - try { - String text = StringUtil.convertLineSeparators(FileUtil.loadFile(new File(getBasePath(), name))); - Document tempDocument = EditorFactory.getInstance().createDocument(text); - if (removeMarkers) { - EditorTestUtil.extractCaretAndSelectionMarkers(tempDocument); - } - List placeholders = getPlaceholders(tempDocument, useLength); - return Pair.create(tempDocument, placeholders); - } - catch (IOException e) { - LOG.error(e); - } - return Pair.create(null, null); - } - - @Override - protected String getTestDataPath() { - return getBasePath(); - } -} - - diff --git a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestsUtil.java b/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestsUtil.java deleted file mode 100644 index 92b8ecf630d1..000000000000 --- a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/CCTestsUtil.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.google.common.collect.Collections2; -import com.intellij.openapi.util.text.StringUtil; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; - -import java.util.Collection; -import java.util.List; - -public class CCTestsUtil { - public static final String BEFORE_POSTFIX = "_before.txt"; - public static final String AFTER_POSTFIX = "_after.txt"; - - private CCTestsUtil() { - } - - public static boolean comparePlaceholders(AnswerPlaceholder p1, AnswerPlaceholder p2) { - if (p1.getOffset() != p2.getOffset()) return false; - if (p1.getRealLength() != p2.getRealLength()) return false; - if (p1.getPossibleAnswer() != null ? !p1.getPossibleAnswer().equals(p2.getPossibleAnswer()) : p2.getPossibleAnswer() != null) return false; - if (p1.getTaskText() != null ? !p1.getTaskText().equals(p2.getTaskText()) : p2.getTaskText() != null) return false; - if (!p1.getHints().equals(p2.getHints())) return false; - return true; - } - - public static String getPlaceholderPresentation(AnswerPlaceholder placeholder) { - return "offset=" + placeholder.getOffset() + - " length=" + placeholder.getLength() + - " possibleAnswer=" + placeholder.getPossibleAnswer() + - " taskText=" + placeholder.getTaskText(); - } - - public static String getPlaceholdersPresentation(List placeholders) { - Collection transformed = Collections2.transform(placeholders, placeholder -> getPlaceholderPresentation(placeholder)); - return "[" + StringUtil.join(transformed, ",") + "]"; - } -} diff --git a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderActionTest.java b/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderActionTest.java deleted file mode 100644 index 801b5096e1bc..000000000000 --- a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCAnswerPlaceholderActionTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.command.undo.UndoManager; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.coursecreator.CCTestCase; -import com.jetbrains.edu.coursecreator.CCTestsUtil; -import com.jetbrains.edu.coursecreator.actions.placeholder.CCAddAnswerPlaceholder; -import com.jetbrains.edu.coursecreator.actions.placeholder.CCDeleteAnswerPlaceholder; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.coursecreator.actions.placeholder.CCCreateAnswerPlaceholderDialog; - -import java.util.Collections; -import java.util.List; - -public class CCAnswerPlaceholderActionTest extends CCTestCase { - static class CCTestAction extends CCAddAnswerPlaceholder { - @Override - protected CCCreateAnswerPlaceholderDialog createDialog(Project project, AnswerPlaceholder answerPlaceholder) { - String placeholderText = answerPlaceholder.getTaskText(); - return new CCCreateAnswerPlaceholderDialog(project, placeholderText == null ? "type here" : placeholderText, answerPlaceholder.getHints()) { - @Override - public boolean showAndGet() { - return true; - } - - @Override - public String getTaskText() { - return "type here"; - } - - @Override - public List getHints() { - return Collections.singletonList("Test hint"); - } - }; - } - } - - public void testPlaceholderWithSelection() { - doTest("onePlaceholder", new CCTestAction()); - } - - public void testPlaceholderWithoutSelection() { - doTest("withoutSelection", new CCTestAction()); - } - - public void testPlaceholderIntersection() { - configureByTaskFile("placeholderIntersection.txt"); - Presentation presentation = myFixture.testAction(new CCTestAction()); - assertTrue(presentation.isVisible() && !presentation.isEnabled()); - } - - public void testPlaceholderDeleted() { - doTest("deletePlaceholder", new CCDeleteAnswerPlaceholder()); - } - - private void doTest(String name, AnAction action) { - VirtualFile virtualFile = configureByTaskFile(name + CCTestsUtil.BEFORE_POSTFIX); - myFixture.testAction(action); - TaskFile taskFile = StudyUtils.getTaskFile(getProject(), virtualFile); - checkByFile(taskFile, name + CCTestsUtil.AFTER_POSTFIX, false); - checkHighlighters(taskFile, myFixture.getEditor().getMarkupModel()); - UndoManager.getInstance(getProject()).undo(FileEditorManager.getInstance(getProject()).getSelectedEditor(virtualFile)); - checkByFile(taskFile, name + CCTestsUtil.BEFORE_POSTFIX, false); - } - - @Override - protected String getBasePath() { - return super.getBasePath() + "/actions/addPlaceholder"; - } -} diff --git a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCShowPreviewTest.java b/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCShowPreviewTest.java deleted file mode 100644 index 7afd957c7527..000000000000 --- a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCShowPreviewTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.testFramework.MapDataContext; -import com.intellij.testFramework.TestActionEvent; -import com.jetbrains.edu.coursecreator.CCTestCase; -import com.jetbrains.edu.coursecreator.CCTestsUtil; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; - -import java.util.List; - -public class CCShowPreviewTest extends CCTestCase { - - public void testPreviewUnavailable() { - VirtualFile file = configureByTaskFile("noplaceholders.txt"); - CCShowPreview action = new CCShowPreview(); - TestActionEvent e = getActionEvent(action, PsiManager.getInstance(getProject()).findFile(file)); - action.beforeActionPerformedUpdate(e); - assertTrue(e.getPresentation().isEnabled() && e.getPresentation().isVisible()); - try { - action.actionPerformed(e); - assertTrue("No message shown", false); - } catch (RuntimeException ex) { - assertEquals(CCShowPreview.NO_PREVIEW_MESSAGE, ex.getMessage()); - } - } - - public void testOnePlaceholder() { - doTest("test"); - } - - public void testSeveralPlaceholders() { - doTest("several"); - } - - private void doTest(String name) { - VirtualFile file = configureByTaskFile(name + CCTestsUtil.BEFORE_POSTFIX); - CCShowPreview action = new CCShowPreview(); - TestActionEvent e = getActionEvent(action,PsiManager.getInstance(getProject()).findFile(file)); - action.beforeActionPerformedUpdate(e); - assertTrue(e.getPresentation().isEnabled() && e.getPresentation().isVisible()); - action.actionPerformed(e); - Editor editor = EditorFactory.getInstance().getAllEditors()[1]; - try { - Pair> pair = getPlaceholders(name + CCTestsUtil.AFTER_POSTFIX); - assertEquals("Files don't match", pair.getFirst().getText(), editor.getDocument().getText()); - for (AnswerPlaceholder placeholder : pair.getSecond()) { - assertNotNull("No highlighter for placeholder:" + CCTestsUtil.getPlaceholderPresentation(placeholder), - getHighlighter(editor.getMarkupModel(), placeholder)); - } - } - finally { - EditorFactory.getInstance().releaseEditor(editor); - } - } - - @Override - protected String getBasePath() { - return super.getBasePath() + "/actions/preview"; - } - - TestActionEvent getActionEvent(AnAction action, PsiFile psiFile) { - MapDataContext context = new MapDataContext(); - context.put(CommonDataKeys.PSI_FILE, psiFile); - context.put(CommonDataKeys.PROJECT, getProject()); - context.put(LangDataKeys.MODULE, myFixture.getModule()); - return new TestActionEvent(context, action); - } -} diff --git a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionTest.java b/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionTest.java deleted file mode 100644 index ab862d67d320..000000000000 --- a/python/educational-core/testSrc/com/jetbrains/edu/coursecreator/actions/CCTaskFileActionTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.jetbrains.edu.coursecreator.actions; - -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.command.undo.UndoManager; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.testFramework.MapDataContext; -import com.intellij.testFramework.TestActionEvent; -import com.jetbrains.edu.coursecreator.CCTestCase; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; - -public class CCTaskFileActionTest extends CCTestCase { - public void testHideTaskFile() { - VirtualFile virtualFile = configureByTaskFile("taskFile.txt"); - launchAction(virtualFile, new CCHideFromStudent()); - assertNull(StudyUtils.getTaskFile(getProject(), virtualFile)); - UndoManager.getInstance(getProject()).undo(FileEditorManager.getInstance(getProject()).getSelectedEditor(virtualFile)); - TaskFile taskFile = StudyUtils.getTaskFile(getProject(), virtualFile); - assertNotNull(taskFile); - checkHighlighters(taskFile, myFixture.getEditor().getMarkupModel()); - } - - public void testAddTaskFile() { - VirtualFile virtualFile = copyFileToTask("nonTaskFile.txt"); - myFixture.configureFromExistingVirtualFile(virtualFile); - launchAction(virtualFile, new CCAddAsTaskFile()); - TaskFile taskFile = StudyUtils.getTaskFile(getProject(), virtualFile); - assertNotNull(taskFile); - FileEditor fileEditor = FileEditorManager.getInstance(getProject()).getSelectedEditor(virtualFile); - UndoManager.getInstance(getProject()).undo(fileEditor); - assertNull(StudyUtils.getTaskFile(getProject(), virtualFile)); - } - - private void launchAction(VirtualFile virtualFile, AnAction action) { - TestActionEvent e = getActionEvent(virtualFile, action); - action.beforeActionPerformedUpdate(e); - assertTrue(e.getPresentation().isEnabled() && e.getPresentation().isVisible()); - action.actionPerformed(e); - } - - @NotNull - private TestActionEvent getActionEvent(VirtualFile virtualFile, AnAction action) { - MapDataContext context = new MapDataContext(); - context.put(CommonDataKeys.VIRTUAL_FILE_ARRAY, new VirtualFile[]{virtualFile}); - context.put(CommonDataKeys.PROJECT, getProject()); - return new TestActionEvent(context, action); - } - - @Override - protected String getBasePath() { - return super.getBasePath() + "/actions/taskFileActions"; - } -} diff --git a/python/educational-core/testSrc/com/jetbrains/edu/learning/StudyMigrationTest.java b/python/educational-core/testSrc/com/jetbrains/edu/learning/StudyMigrationTest.java deleted file mode 100644 index eb355a7a781c..000000000000 --- a/python/educational-core/testSrc/com/jetbrains/edu/learning/StudyMigrationTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.openapi.util.JDOMUtil; -import com.intellij.testFramework.PlatformTestUtil; -import com.intellij.util.JdomKt; -import org.jdom.Element; -import org.jdom.JDOMException; -import org.jetbrains.annotations.NotNull; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -import static org.junit.Assert.assertTrue; - -public class StudyMigrationTest { - @Rule public TestName name = new TestName(); - - @Test - public void testFromThirdToForth() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException { - doTest(3); - } - - @Test - public void testAdaptive45() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException { - doTest(4); - } - - @Test - public void testSubtasks45() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException { - doTest(4); - } - - @Test - public void testTheory35To4() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException { - doTest(4); - } - - @Test - public void testTheory351To4() throws JDOMException, IOException, StudySerializationUtils.StudyUnrecognizedFormatException { - doTest(4); - } - - private void doTest(int version) throws IOException, JDOMException, StudySerializationUtils.StudyUnrecognizedFormatException { - final String name = PlatformTestUtil.getTestName(this.name.getMethodName(), true); - final Path before = getTestDataPath().resolve(name + ".xml"); - final Path after = getTestDataPath().resolve(name + ".after.xml"); - Element element = JdomKt.loadElement(before); - Element converted = element; - switch (version) { - case 1: - converted = StudySerializationUtils.Xml.convertToSecondVersion(element); - break; - case 3: - converted = StudySerializationUtils.Xml.convertToForthVersion(element); - break; - case 4: - converted = StudySerializationUtils.Xml.convertToFifthVersion(element); - break; - } - assertTrue(JDOMUtil.areElementsEqual(converted, JdomKt.loadElement(after))); - } - - @NotNull - protected Path getTestDataPath() { - return Paths.get("testData/migration"); - } -} diff --git a/python/educational-core/testSrc/com/jetbrains/edu/learning/stepic/StudyStepicFormatTest.java b/python/educational-core/testSrc/com/jetbrains/edu/learning/stepic/StudyStepicFormatTest.java deleted file mode 100644 index 46d4086bc631..000000000000 --- a/python/educational-core/testSrc/com/jetbrains/edu/learning/stepic/StudyStepicFormatTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.jetbrains.edu.learning.stepic; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.util.containers.ContainerUtil; -import com.jetbrains.edu.learning.StudySerializationUtils; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholderSubtaskInfo; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static org.junit.Assert.*; - - -public class StudyStepicFormatTest { - - @Test - public void fromFirstVersion() throws IOException { - doStepOptionsCreationTest("1.json"); - } - - @Test - public void fromSecondVersion() throws IOException { - doStepOptionsCreationTest("2.json"); - } - - @Test - public void testWithSubtasks() throws IOException { - StepicWrappers.StepOptions stepOptions = doStepOptionsCreationTest("3.json"); - assertEquals(1, stepOptions.lastSubtaskIndex); - } - - - private static StepicWrappers.StepOptions doStepOptionsCreationTest(String fileName) throws IOException { - String responseString = - FileUtil.loadFile(new File(getTestDataPath(), fileName)); - StepicWrappers.StepSource stepSource = - EduStepicClient.deserializeStepicResponse(StepicWrappers.StepContainer.class, responseString).steps.get(0); - StepicWrappers.StepOptions options = stepSource.block.options; - List files = options.files; - assertTrue("Wrong number of task files", files.size() == 1); - List placeholders = files.get(0).getAnswerPlaceholders(); - assertTrue("Wrong number of placeholders", placeholders.size() == 1); - Map infos = placeholders.get(0).getSubtaskInfos(); - assertNotNull(infos); - assertEquals(Collections.singletonList("Type your name here."), infos.get(0).getHints()); - assertEquals("Liana", infos.get(0).getPossibleAnswer()); - return options; - } - - @Test - public void testAvailableCourses() throws IOException { - String responseString = FileUtil.loadFile(new File(getTestDataPath(), "courses.json")); - StepicWrappers.CoursesContainer container = - EduStepicClient.deserializeStepicResponse(StepicWrappers.CoursesContainer.class, responseString); - assertNotNull(container.courses); - assertTrue("Incorrect number of courses", container.courses.size() == 4); - } - - @Test - public void placeholderSerialization() throws IOException { - final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation(). - registerTypeAdapter(AnswerPlaceholder.class, new StudySerializationUtils.Json.StepicAnswerPlaceholderAdapter()).create(); - AnswerPlaceholder answerPlaceholder = new AnswerPlaceholder(); - answerPlaceholder.setOffset(1); - answerPlaceholder.setLength(10); - AnswerPlaceholderSubtaskInfo info1 = createSubtaskInfo("type here", "answer1", ContainerUtil.list("hint 1", "hint 2")); - AnswerPlaceholderSubtaskInfo info2 = createSubtaskInfo("type here1", "answer2", ContainerUtil.list("hint 11", "hint 22")); - answerPlaceholder.setSubtaskInfos(ContainerUtil.newHashMap(ContainerUtil.list(0, 1), ContainerUtil.list(info1, info2))); - final String placeholderSerialization = gson.toJson(answerPlaceholder); - String expected = FileUtil.loadFile(new File(getTestDataPath(), "placeholder.json")); - JsonObject object = new JsonParser().parse(expected).getAsJsonObject(); - StudySerializationUtils.Json.removeIndexFromSubtaskInfos(object); - assertEquals(gson.toJson(gson.fromJson(object, AnswerPlaceholder.class)), placeholderSerialization); - - } - - private static AnswerPlaceholderSubtaskInfo createSubtaskInfo(String placeholderText, String possibleAnswer, List hints) { - AnswerPlaceholderSubtaskInfo info = new AnswerPlaceholderSubtaskInfo(); - info.setPlaceholderText(placeholderText); - info.setPossibleAnswer(possibleAnswer); - info.setHints(hints); - info.setNeedInsertText(true); - return info; - } - - @NotNull - private static String getTestDataPath() { - return FileUtil.join("testData/stepic"); - } -} diff --git a/python/educational-python/Edu-Python/Edu-Python.iml b/python/educational-python/Edu-Python/Edu-Python.iml deleted file mode 100644 index eb6c8902c6b1..000000000000 --- a/python/educational-python/Edu-Python/Edu-Python.iml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/python/educational-python/Edu-Python/resources/META-INF/plugin.xml b/python/educational-python/Edu-Python/resources/META-INF/plugin.xml deleted file mode 100644 index f90410a1bd20..000000000000 --- a/python/educational-python/Edu-Python/resources/META-INF/plugin.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - com.jetbrains.python.edu.interactivelearning.python - Edu Python - 4.1-IJ2017.3-1 - - - JetBrains - - - - - com.intellij.modules.lang - com.intellij.modules.python - com.jetbrains.edu.interactivelearning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - com.jetbrains.edu.learning.PyStudyShowTutorial - com.jetbrains.edu.learning.PyStudyShowTutorial - - - - - - com.jetbrains.edu.learning.PyStudyInitialConfigurator - - - - \ No newline at end of file diff --git a/python/educational-python/Edu-Python/resources/com/jetbrains/python/edu/user_tester.py b/python/educational-python/Edu-Python/resources/com/jetbrains/python/edu/user_tester.py deleted file mode 100644 index 4cef99c7865f..000000000000 --- a/python/educational-python/Edu-Python/resources/com/jetbrains/python/edu/user_tester.py +++ /dev/null @@ -1,58 +0,0 @@ -import imp -import os -import subprocess -import sys - -USER_TESTS = "userTests" - -TEST_FAILED = "FAILED" - -TEST_PASSED = "PASSED" - -INPUT = "input" -OUTPUT = "output" - - -def get_index(logical_name, full_name): - logical_name_len = len(logical_name) - if full_name[:logical_name_len] == logical_name: - return int(full_name[logical_name_len]) - return -1 - - -def process_user_tests(file_path): - user_tests = [] - imp.load_source('user_file', file_path) - user_tests_dir_path = os.path.abspath(os.path.join(file_path, os.pardir, USER_TESTS)) - user_test_files = os.listdir(user_tests_dir_path) - for user_file in user_test_files: - index = get_index(INPUT, user_file) - if index == -1: - continue - output = OUTPUT + str(index) - if output in user_test_files: - input_path = os.path.abspath(os.path.join(user_tests_dir_path, user_file)) - output_path = os.path.abspath(os.path.join(user_tests_dir_path, output)) - user_tests.append((input_path, output_path, index)) - return sorted(user_tests, key=(lambda x: x[2])) - - -def run_user_test(python, executable_path): - user_tests = process_user_tests(executable_path) - for test in user_tests: - input, output, index = test - test_output = subprocess.check_output([python, executable_path, input]) - expected_output = open(output).read() - test_status = TEST_PASSED if test_output == expected_output else TEST_FAILED - print "TEST" + str(index) + " " + test_status - print "OUTPUT:" - print test_output + "\n" - if test_status == TEST_FAILED: - print "EXPECTED OUTPUT:" - print expected_output + "\n" - - -if __name__ == "__main__": - python = sys.argv[1] - executable_path = sys.argv[2] - run_user_test(python , executable_path) \ No newline at end of file diff --git a/python/educational-python/Edu-Python/resources/courses/Introduction to Python.zip b/python/educational-python/Edu-Python/resources/courses/Introduction to Python.zip deleted file mode 100644 index d8861d5589783e94c9fea8b58bd946e81703cac0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20206 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 diff --git a/python/educational-python/Edu-Python/resources/inspectionDescriptions/PyStudyInterpreterInspection.html b/python/educational-python/Edu-Python/resources/inspectionDescriptions/PyStudyInterpreterInspection.html deleted file mode 100644 index 1e99730fe501..000000000000 --- a/python/educational-python/Edu-Python/resources/inspectionDescriptions/PyStudyInterpreterInspection.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -This inspection notifies you if the current project Python interpreter version is different than the course one. - - \ No newline at end of file diff --git a/python/educational-python/Edu-Python/resources/python.js b/python/educational-python/Edu-Python/resources/python.js deleted file mode 100644 index c4b84da948ae..000000000000 --- a/python/educational-python/Edu-Python/resources/python.js +++ /dev/null @@ -1,348 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: http://codemirror.net/LICENSE - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - function wordRegexp(words) { - return new RegExp("^((" + words.join(")|(") + "))\\b"); - } - - var wordOperators = wordRegexp(["and", "or", "not", "is"]); - var commonKeywords = ["as", "assert", "break", "class", "continue", - "def", "del", "elif", "else", "except", "finally", - "for", "from", "global", "if", "import", - "lambda", "pass", "raise", "return", - "try", "while", "with", "yield", "in"]; - var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr", - "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", - "enumerate", "eval", "filter", "float", "format", "frozenset", - "getattr", "globals", "hasattr", "hash", "help", "hex", "id", - "input", "int", "isinstance", "issubclass", "iter", "len", - "list", "locals", "map", "max", "memoryview", "min", "next", - "object", "oct", "open", "ord", "pow", "property", "range", - "repr", "reversed", "round", "set", "setattr", "slice", - "sorted", "staticmethod", "str", "sum", "super", "tuple", - "type", "vars", "zip", "__import__", "NotImplemented", - "Ellipsis", "__debug__"]; - var py2 = {builtins: ["apply", "basestring", "buffer", "cmp", "coerce", "execfile", - "file", "intern", "long", "raw_input", "reduce", "reload", - "unichr", "unicode", "xrange", "False", "True", "None"], - keywords: ["exec", "print"]}; - var py3 = {builtins: ["ascii", "bytes", "exec", "print"], - keywords: ["nonlocal", "False", "True", "None", "async", "await"]}; - - CodeMirror.registerHelper("hintWords", "python", commonKeywords.concat(commonBuiltins)); - - function top(state) { - return state.scopes[state.scopes.length - 1]; - } - - CodeMirror.defineMode("python", function(conf, parserConf) { - var ERRORCLASS = "error"; - - var singleDelimiters = parserConf.singleDelimiters || /^[\(\)\[\]\{\}@,:`=;\.]/; - var doubleOperators = parserConf.doubleOperators || /^([!<>]==|<>|<<|>>|\/\/|\*\*)/; - var doubleDelimiters = parserConf.doubleDelimiters || /^(\+=|\-=|\*=|%=|\/=|&=|\|=|\^=)/; - var tripleDelimiters = parserConf.tripleDelimiters || /^(\/\/=|>>=|<<=|\*\*=)/; - - if (parserConf.version && parseInt(parserConf.version, 10) == 3) { - // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator - var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!@]/; - var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/; - } else { - var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!]/; - var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/; - } - - var hangingIndent = parserConf.hangingIndent || conf.indentUnit; - - var myKeywords = commonKeywords, myBuiltins = commonBuiltins; - if (parserConf.extra_keywords != undefined) - myKeywords = myKeywords.concat(parserConf.extra_keywords); - - if (parserConf.extra_builtins != undefined) - myBuiltins = myBuiltins.concat(parserConf.extra_builtins); - - if (parserConf.version && parseInt(parserConf.version, 10) == 3) { - myKeywords = myKeywords.concat(py3.keywords); - myBuiltins = myBuiltins.concat(py3.builtins); - var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i"); - } else { - myKeywords = myKeywords.concat(py2.keywords); - myBuiltins = myBuiltins.concat(py2.builtins); - var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i"); - } - var keywords = wordRegexp(myKeywords); - var builtins = wordRegexp(myBuiltins); - - // tokenizers - function tokenBase(stream, state) { - // Handle scope changes - if (stream.sol() && top(state).type == "py") { - state.indent = stream.indentation() - var scopeOffset = top(state).offset; - if (stream.eatSpace()) { - var lineOffset = stream.indentation(); - if (lineOffset > scopeOffset) - pushPyScope(state); - else if (lineOffset < scopeOffset && dedent(stream, state)) - state.errorToken = true; - return null; - } else { - var style = tokenBaseInner(stream, state); - if (scopeOffset > 0 && dedent(stream, state)) - style += " " + ERRORCLASS; - return style; - } - } - return tokenBaseInner(stream, state); - } - - function tokenBaseInner(stream, state) { - if (stream.eatSpace()) return null; - - var ch = stream.peek(); - - // Handle Comments - if (ch == "#") { - stream.skipToEnd(); - return "comment"; - } - - // Handle Number Literals - if (stream.match(/^[0-9\.]/, false)) { - var floatLiteral = false; - // Floats - if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; } - if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; } - if (stream.match(/^\.\d+/)) { floatLiteral = true; } - if (floatLiteral) { - // Float literals may be "imaginary" - stream.eat(/J/i); - return "number"; - } - // Integers - var intLiteral = false; - // Hex - if (stream.match(/^0x[0-9a-f]+/i)) intLiteral = true; - // Binary - if (stream.match(/^0b[01]+/i)) intLiteral = true; - // Octal - if (stream.match(/^0o[0-7]+/i)) intLiteral = true; - // Decimal - if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) { - // Decimal literals may be "imaginary" - stream.eat(/J/i); - // TODO - Can you have imaginary longs? - intLiteral = true; - } - // Zero by itself with no other piece of number. - if (stream.match(/^0(?![\dx])/i)) intLiteral = true; - if (intLiteral) { - // Integer literals may be "long" - stream.eat(/L/i); - return "number"; - } - } - - // Handle Strings - if (stream.match(stringPrefixes)) { - state.tokenize = tokenStringFactory(stream.current()); - return state.tokenize(stream, state); - } - - // Handle operators and Delimiters - if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) - return "punctuation"; - - if (stream.match(doubleOperators) || stream.match(singleOperators)) - return "operator"; - - if (stream.match(singleDelimiters)) - return "punctuation"; - - if (state.lastToken == "." && stream.match(identifiers)) - return "property"; - - if (stream.match(keywords) || stream.match(wordOperators)) - return "keyword"; - - if (stream.match(builtins)) - return "builtin"; - - if (stream.match(/^(self|cls)\b/)) - return "variable-2"; - - if (stream.match(identifiers)) { - if (state.lastToken == "def" || state.lastToken == "class") - return "def"; - return "variable"; - } - - // Handle non-detected items - stream.next(); - return ERRORCLASS; - } - - function tokenStringFactory(delimiter) { - while ("rub".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) - delimiter = delimiter.substr(1); - - var singleline = delimiter.length == 1; - var OUTCLASS = "string"; - - function tokenString(stream, state) { - while (!stream.eol()) { - stream.eatWhile(/[^'"\\]/); - if (stream.eat("\\")) { - stream.next(); - if (singleline && stream.eol()) - return OUTCLASS; - } else if (stream.match(delimiter)) { - state.tokenize = tokenBase; - return OUTCLASS; - } else { - stream.eat(/['"]/); - } - } - if (singleline) { - if (parserConf.singleLineStringErrors) - return ERRORCLASS; - else - state.tokenize = tokenBase; - } - return OUTCLASS; - } - tokenString.isString = true; - return tokenString; - } - - function pushPyScope(state) { - while (top(state).type != "py") state.scopes.pop() - state.scopes.push({offset: top(state).offset + conf.indentUnit, - type: "py", - align: null}) - } - - function pushBracketScope(stream, state, type) { - var align = stream.match(/^([\s\[\{\(]|#.*)*$/, false) ? null : stream.column() + 1 - state.scopes.push({offset: state.indent + hangingIndent, - type: type, - align: align}) - } - - function dedent(stream, state) { - var indented = stream.indentation(); - while (top(state).offset > indented) { - if (top(state).type != "py") return true; - state.scopes.pop(); - } - return top(state).offset != indented; - } - - function tokenLexer(stream, state) { - var style = state.tokenize(stream, state); - var current = stream.current(); - - // Handle decorators - if (current == "@") { - if (parserConf.version && parseInt(parserConf.version, 10) == 3) - return stream.match(identifiers, false) ? "meta" : "operator"; - else - return stream.match(identifiers, false) ? "meta" : ERRORCLASS; - } - - if ((style == "variable" || style == "builtin") - && state.lastToken == "meta") - style = "meta"; - - // Handle scope changes. - if (current == "pass" || current == "return") - state.dedent += 1; - - if (current == "lambda") state.lambda = true; - if (current == ":" && !state.lambda && top(state).type == "py") - pushPyScope(state); - - var delimiter_index = current.length == 1 ? "[({".indexOf(current) : -1; - if (delimiter_index != -1) - pushBracketScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1)); - - delimiter_index = "])}".indexOf(current); - if (delimiter_index != -1) { - if (top(state).type == current) state.indent = state.scopes.pop().offset - hangingIndent - else return ERRORCLASS; - } - if (state.dedent > 0 && stream.eol() && top(state).type == "py") { - if (state.scopes.length > 1) state.scopes.pop(); - state.dedent -= 1; - } - - return style; - } - - var external = { - startState: function(basecolumn) { - return { - tokenize: tokenBase, - scopes: [{offset: basecolumn || 0, type: "py", align: null}], - indent: basecolumn || 0, - lastToken: null, - lambda: false, - dedent: 0 - }; - }, - - token: function(stream, state) { - var addErr = state.errorToken; - if (addErr) state.errorToken = false; - var style = tokenLexer(stream, state); - - if (style && style != "comment") - state.lastToken = (style == "keyword" || style == "punctuation") ? stream.current() : style; - if (style == "punctuation") style = null; - - if (stream.eol() && state.lambda) - state.lambda = false; - return addErr ? style + " " + ERRORCLASS : style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase) - return state.tokenize.isString ? CodeMirror.Pass : 0; - - var scope = top(state), closing = scope.type == textAfter.charAt(0) - if (scope.align != null) - return scope.align - (closing ? 1 : 0) - else - return scope.offset - (closing ? hangingIndent : 0) - }, - - electricInput: /^\s*[\}\]\)]$/, - closeBrackets: {triples: "'\""}, - lineComment: "#", - fold: "indent" - }; - return external; - }); - - CodeMirror.defineMIME("text/x-python", "python"); - - var words = function(str) { return str.split(" "); }; - - CodeMirror.defineMIME("text/x-cython", { - name: "python", - extra_keywords: words("by cdef cimport cpdef ctypedef enum except"+ - "extern gil include nogil property public"+ - "readonly struct union DEF IF ELIF ELSE") - }); - -}); diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/CreateFromArchiveProjectStep.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/CreateFromArchiveProjectStep.java deleted file mode 100644 index ea4a9dafdab3..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/CreateFromArchiveProjectStep.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.platform.DirectoryProjectGenerator; -import com.jetbrains.edu.coursecreator.actions.CCCreateCourseArchive; -import com.jetbrains.edu.learning.PyStudyDirectoryProjectGenerator; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; -import com.jetbrains.python.newProject.steps.PyCharmNewProjectStep; -import org.jetbrains.annotations.NotNull; - -class CreateFromArchiveProjectStep extends PyCharmNewProjectStep { - - public CreateFromArchiveProjectStep(Project project, Module module) { - super(new MyCustomization(project, module)); - } - - protected static class MyCustomization extends PyCharmNewProjectStep.Customization { - - private final Project myProject; - private final Module myModule; - private PyStudyDirectoryProjectGenerator myGenerator = new PyStudyDirectoryProjectGenerator(true); - - public MyCustomization(Project project, - Module module) { - - myProject = project; - myModule = module; - } - - @NotNull - @Override - protected DirectoryProjectGenerator[] getProjectGenerators() { - return new DirectoryProjectGenerator[] {}; - } - - - @NotNull - @Override - protected DirectoryProjectGenerator createEmptyProjectGenerator() { - Course currentCourse = StudyTaskManager.getInstance(myProject).getCourse(); - if (currentCourse != null) { - VirtualFile folder = CCUtils.getGeneratedFilesFolder(myProject, myModule); - String zipName = FileUtil.sanitizeFileName(currentCourse.getName()); - if (zipName.isEmpty()) { - zipName = EduNames.COURSE; - } - String locationDir = folder.getPath(); - CCCreateCourseArchive.createCourseArchive(myProject, myModule, zipName, locationDir, false); - String path = FileUtil.join(FileUtil.toSystemDependentName(folder.getPath()), zipName + ".zip"); - StudyProjectGenerator generator = myGenerator.getGenerator(); - Course course = generator.addLocalCourse(path); - assert course != null; - generator.setSelectedCourse(course); - } - return myGenerator; - } - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCChangeCourseInfo.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCChangeCourseInfo.java deleted file mode 100644 index 574560b4919f..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCChangeCourseInfo.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.openapi.ui.ComboBox; -import com.jetbrains.edu.coursecreator.actions.CCChangeCourseInfo; -import com.jetbrains.edu.coursecreator.ui.CCNewProjectPanel; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.python.PythonLanguage; -import com.jetbrains.python.psi.LanguageLevel; - -import javax.swing.*; - -import static com.jetbrains.edu.learning.PyEduPluginConfigurator.PYTHON_2; -import static com.jetbrains.edu.learning.PyEduPluginConfigurator.PYTHON_3; - -public class PyCCChangeCourseInfo extends CCChangeCourseInfo { - public static final String ALL_VERSIONS = "All versions"; - - public PyCCChangeCourseInfo() { - super(); - } - - protected void setVersion(Course course, CCNewProjectPanel panel) { - String version = panel.getLanguageVersion(); - String language = PythonLanguage.getInstance().getID(); - if (version != null && !ALL_VERSIONS.equals(version)) { - language += " " + version; - } - course.setLanguage(language); - } - - protected void setupLanguageLevels(Course course, CCNewProjectPanel panel) { - JLabel languageLevelLabel = panel.getLanguageLevelLabel(); - languageLevelLabel.setText("Python:"); - languageLevelLabel.setVisible(true); - ComboBox languageLevelCombobox = panel.getLanguageLevelCombobox(); - languageLevelCombobox.addItem(ALL_VERSIONS); - languageLevelCombobox.addItem(PYTHON_3); - languageLevelCombobox.addItem(PYTHON_2); - for (LanguageLevel level : LanguageLevel.values()) { - languageLevelCombobox.addItem(level.toString()); - } - languageLevelCombobox.setVisible(true); - final String version = course.getLanguageVersion(); - if (version != null) { - languageLevelCombobox.setSelectedItem(version); - } - else { - languageLevelCombobox.setSelectedItem(ALL_VERSIONS); - } - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateCourseAction.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateCourseAction.java deleted file mode 100644 index 42c3f65aa2d4..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateCourseAction.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.ide.util.projectWizard.AbstractNewProjectDialog; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.actionSystem.Presentation; -import icons.EducationalCoreIcons; - -import static com.jetbrains.edu.coursecreator.actions.CCPluginToggleAction.COURSE_CREATOR_ENABLED; - -public class PyCCCreateCourseAction extends AnAction { - public PyCCCreateCourseAction() { - super("Create New Course", "Create New Course", EducationalCoreIcons.EducationalProjectType); - } - - @Override - public void actionPerformed(AnActionEvent e) { - AbstractNewProjectDialog dialog = new AbstractNewProjectDialog() { - @Override - protected DefaultActionGroup createRootStep() { - return new PyCCCreateCourseProjectStep(); - } - }; - dialog.show(); - } - - @Override - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - presentation.setEnabledAndVisible(PropertiesComponent.getInstance().getBoolean(COURSE_CREATOR_ENABLED)); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateCourseProjectStep.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateCourseProjectStep.java deleted file mode 100644 index 4badb799a604..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateCourseProjectStep.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.platform.DirectoryProjectGenerator; -import com.jetbrains.python.newProject.steps.PyCharmNewProjectStep; -import org.jetbrains.annotations.NotNull; - -class PyCCCreateCourseProjectStep extends PyCharmNewProjectStep { - - public PyCCCreateCourseProjectStep() { - super(new MyCustomization()); - } - - protected static class MyCustomization extends Customization { - private final PyCCProjectGenerator myGenerator = new PyCCProjectGenerator(); - public MyCustomization() { - } - - @NotNull - @Override - protected DirectoryProjectGenerator[] getProjectGenerators() { - return new DirectoryProjectGenerator[] {}; - } - - @NotNull - @Override - protected DirectoryProjectGenerator createEmptyProjectGenerator() { - return myGenerator; - } - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateProjectFromArchive.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateProjectFromArchive.java deleted file mode 100644 index a9cf061e8a21..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCCreateProjectFromArchive.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.ide.util.projectWizard.AbstractNewProjectDialog; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.project.Project; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; - -public class PyCCCreateProjectFromArchive extends DumbAwareAction { - public PyCCCreateProjectFromArchive() { - super("Preview Course"); - } - - @Override - public void actionPerformed(AnActionEvent e) { - final Project project = e.getData(CommonDataKeys.PROJECT); - final Module module = e.getData(LangDataKeys.MODULE); - if (project == null || module == null) { - return; - } - - EduUsagesCollector.coursePreviewed(); - AbstractNewProjectDialog dialog = new AbstractNewProjectDialog() { - @Override - protected DefaultActionGroup createRootStep() { - return new CreateFromArchiveProjectStep(project, module); - } - }; - dialog.show(); - } - - @Override - public void update(AnActionEvent e) { - Presentation presentation = e.getPresentation(); - Project project = e.getProject(); - presentation.setEnabledAndVisible(project != null && CCUtils.isCourseCreator(project)); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCProjectGenerator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCProjectGenerator.java deleted file mode 100644 index 51e07a04b9fe..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/PyCCProjectGenerator.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.jetbrains.edu.coursecreator; - -import com.intellij.facet.ui.FacetEditorValidator; -import com.intellij.facet.ui.FacetValidatorsManager; -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.command.WriteCommandAction; -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.util.io.FileUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiManager; -import com.jetbrains.edu.coursecreator.actions.CCCreateLesson; -import com.jetbrains.edu.coursecreator.actions.CCCreateTask; -import com.jetbrains.edu.coursecreator.ui.CCNewProjectPanel; -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.statistics.EduUsagesCollector; -import com.jetbrains.python.PythonLanguage; -import com.jetbrains.python.newProject.PyNewProjectSettings; -import com.jetbrains.python.newProject.PythonProjectGenerator; -import com.jetbrains.python.remote.PyProjectSynchronizer; -import icons.EducationalCoreIcons; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; - -public class PyCCProjectGenerator extends PythonProjectGenerator { - private static final Logger LOG = Logger.getInstance(PyCCProjectGenerator.class); - private CCNewProjectPanel mySettingsPanel; - - @Nls - @NotNull - @Override - public String getName() { - return "Course creation"; - } - - @Nullable - @Override - public Icon getLogo() { - return EducationalCoreIcons.CourseCreationProjectType; - } - - @Override - public void configureProject(@NotNull final Project project, @NotNull final VirtualFile baseDir, - @NotNull PyNewProjectSettings settings, - @NotNull Module module, - @Nullable final PyProjectSynchronizer synchronizer) { - final Course course = getCourse(project, mySettingsPanel); - EduUsagesCollector.projectTypeCreated(CCUtils.COURSE_MODE); - final CCProjectComponent component = project.getComponent(CCProjectComponent.class); - component.registerListener(); - final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(baseDir); - if (projectDir == null) return; - new WriteCommandAction.Simple(project) { - @Override - protected void run() throws Throwable { - - createTestHelper(project, projectDir); - - PsiDirectory lessonDir = new CCCreateLesson().createItem(null, project, projectDir, course); - if (lessonDir == null) { - LOG.error("Failed to create lesson"); - return; - } - new CCCreateTask().createItem(null, project, lessonDir, course); - } - }.execute(); - } - - private static void createTestHelper(@NotNull Project project, PsiDirectory projectDir) { - final FileTemplate template = - FileTemplateManager.getInstance(project).getInternalTemplate(FileUtil.getNameWithoutExtension(EduNames.TEST_HELPER)); - try { - FileTemplateUtil.createFromTemplate(template, EduNames.TEST_HELPER, null, projectDir); - } - catch (Exception ignored) { - } - } - - @NotNull - private static Course getCourse(@NotNull Project project, @NotNull CCNewProjectPanel settingsPanel) { - final Course course = new Course(); - String name = settingsPanel.getName(); - course.setName(name); - course.setAuthorsAsString(settingsPanel.getAuthors()); - course.setDescription(settingsPanel.getDescription()); - - String language = PythonLanguage.getInstance().getID(); - course.setLanguage(language); - course.setCourseMode(CCUtils.COURSE_MODE); - - StudyTaskManager.getInstance(project).setCourse(course); - StudyUtils.registerStudyToolWindow(course, project); - return course; - } - - @NotNull - @Override - public ValidationResult validate(@NotNull String s) { - String message = ""; - message = mySettingsPanel.getDescription().isEmpty() ? "Enter description" : message; - message = mySettingsPanel.getAuthors().length == 0 ? "Enter author name" : message; - message = mySettingsPanel.getName().isEmpty() ? "Enter course name" : message; - return message.isEmpty() ? ValidationResult.OK : new ValidationResult(message); - } - - @Nullable - @Override - public JPanel extendBasePanel() throws ProcessCanceledException { - mySettingsPanel = new CCNewProjectPanel(); - mySettingsPanel.registerValidators(new FacetValidatorsManager() { - public void registerValidator(FacetEditorValidator validator, JComponent... componentsToWatch) { - throw new UnsupportedOperationException(); - } - - public void validate() { - fireStateChanged(); - } - }); - return mySettingsPanel.getMainPanel(); - } - - @Override - public void locationChanged(@NotNull String newLocation) { - mySettingsPanel.getNameField().setText(newLocation); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java deleted file mode 100644 index 77a48f1f6b77..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.jetbrains.edu.coursecreator.run; - -import com.intellij.execution.ExecutionException; -import com.intellij.execution.ExecutionResult; -import com.intellij.execution.Executor; -import com.intellij.execution.configurations.GeneralCommandLine; -import com.intellij.execution.configurations.ParamsGroup; -import com.intellij.execution.process.ProcessAdapter; -import com.intellij.execution.process.ProcessEvent; -import com.intellij.execution.process.ProcessHandler; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.checker.StudyCheckUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.python.run.CommandLinePatcher; -import com.jetbrains.python.run.PythonCommandLineState; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - -public class PyCCCommandLineState extends PythonCommandLineState { - private final PyCCRunTestConfiguration myRunConfiguration; - private final VirtualFile myTaskDir; - private final Task myTask; - - public PyCCCommandLineState(PyCCRunTestConfiguration runConfiguration, - ExecutionEnvironment env) { - super(runConfiguration, env); - myRunConfiguration = runConfiguration; - - VirtualFile testsFile = LocalFileSystem.getInstance().findFileByPath(myRunConfiguration.getPathToTest()); - assert testsFile != null; - myTaskDir = StudyUtils.getTaskDir(testsFile); - assert myTaskDir != null; - myTask = StudyUtils.getTask(myRunConfiguration.getProject(), myTaskDir); - assert myTask != null; - } - - @Override - protected void buildCommandLineParameters(GeneralCommandLine commandLine) { - commandLine.setWorkDirectory(myTaskDir.getPath()); - ParamsGroup group = commandLine.getParametersList().getParamsGroup(GROUP_SCRIPT); - assert group != null; - - Project project = myRunConfiguration.getProject(); - Course course = StudyTaskManager.getInstance(project).getCourse(); - assert course != null; - - group.addParameter(myRunConfiguration.getPathToTest()); - - String path = getCurrentTaskFilePath(); - if (path != null) { - group.addParameter(path); - } - } - - @Nullable - private String getCurrentTaskFilePath() { - String textFile = null; - for (Map.Entry entry : myTask.getTaskFiles().entrySet()) { - String path = getTaskFilePath(entry.getKey()); - if (!entry.getValue().getActivePlaceholders().isEmpty()) { - return path; - } - VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(path); - if (virtualFile == null) { - continue; - } - if (TextEditorProvider.isTextFile(virtualFile)) { - textFile = path; - } - } - return textFile; - } - - - private String getTaskFilePath(String name) { - String taskDirPath = FileUtil.toSystemDependentName(myTaskDir.getPath()); - return myTaskDir.findChild(EduNames.SRC) != null ? - FileUtil.join(taskDirPath, EduNames.SRC, name) : - FileUtil.join(taskDirPath, name); - } - - @Override - public ExecutionResult execute(Executor executor, PythonProcessStarter processStarter, CommandLinePatcher... patchers) throws ExecutionException { - ApplicationManager.getApplication().runWriteAction(() -> StudyCheckUtils.flushWindows(myTask, myTaskDir)); - - return super.execute(executor, processStarter, patchers); - } - - @Override - protected ProcessHandler doCreateProcess(GeneralCommandLine commandLine) throws ExecutionException { - ProcessHandler handler = super.doCreateProcess(commandLine); - handler.addProcessListener(new ProcessAdapter() { - @Override - public void processTerminated(@NotNull ProcessEvent event) { - ApplicationManager.getApplication().invokeLater(() -> EduUtils.deleteWindowDescriptions(myTask, myTaskDir)); - } - }); - return handler; - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestConfiguration.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestConfiguration.java deleted file mode 100644 index aca074e73274..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestConfiguration.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.jetbrains.edu.coursecreator.run; - -import com.intellij.execution.ExecutionException; -import com.intellij.execution.Executor; -import com.intellij.execution.configurations.ConfigurationFactory; -import com.intellij.execution.configurations.RunProfileState; -import com.intellij.execution.configurations.RuntimeConfigurationException; -import com.intellij.execution.runners.ExecutionEnvironment; -import com.intellij.openapi.options.SettingsEditor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.JDOMExternalizerUtil; -import com.intellij.openapi.util.WriteExternalException; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.python.run.AbstractPythonRunConfiguration; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -class PyCCRunTestConfiguration extends AbstractPythonRunConfiguration { - - public static final String PATH_ATTR = "studyTest"; - private Project myProject; - private String myPathToTest; - - public PyCCRunTestConfiguration(Project project, ConfigurationFactory factory) { - super(project, factory); - myProject = project; - } - - @Nullable - @Override - public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) throws ExecutionException { - return new PyCCCommandLineState(this, environment); - } - - @Override - protected SettingsEditor createConfigurationEditor() { - return new PyCCSettingsEditor(myProject); - } - - public String getPathToTest() { - return myPathToTest; - } - - public void setPathToTest(String pathToTest) { - myPathToTest = pathToTest; - } - - @Override - public void readExternal(Element element) throws InvalidDataException { - super.readExternal(element); - myPathToTest = JDOMExternalizerUtil.readField(element, PATH_ATTR); - } - - @Override - public void writeExternal(Element element) throws WriteExternalException { - super.writeExternal(element); - JDOMExternalizerUtil.writeField(element, PATH_ATTR, myPathToTest); - } - - @Override - public void checkConfiguration() throws RuntimeConfigurationException { - super.checkConfiguration(); - String message = "Select valid path to the file with tests"; - VirtualFile testsFile = LocalFileSystem.getInstance().findFileByPath(myPathToTest); - if (testsFile == null) { - throw new RuntimeConfigurationException(message); - } - VirtualFile taskDir = StudyUtils.getTaskDir(testsFile); - if (taskDir == null) { - throw new RuntimeConfigurationException(message); - } - if (StudyUtils.getTask(myProject, taskDir) == null) { - throw new RuntimeConfigurationException(message); - } - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationFactory.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationFactory.java deleted file mode 100644 index 1d2de895d0a2..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationFactory.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.jetbrains.edu.coursecreator.run; - -import com.intellij.execution.configurations.ConfigurationFactory; -import com.intellij.execution.configurations.ConfigurationType; -import com.intellij.execution.configurations.RunConfiguration; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NotNull; - -public class PyCCRunTestsConfigurationFactory extends ConfigurationFactory { - protected PyCCRunTestsConfigurationFactory(@NotNull ConfigurationType type) { - super(type); - } - - @NotNull - @Override - public RunConfiguration createTemplateConfiguration(@NotNull Project project) { - return new PyCCRunTestConfiguration(project, this); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java deleted file mode 100644 index e5c7036e739c..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.jetbrains.edu.coursecreator.run; - -import com.intellij.execution.Location; -import com.intellij.execution.actions.ConfigurationContext; -import com.intellij.execution.actions.RunConfigurationProducer; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiElement; -import com.jetbrains.edu.coursecreator.CCUtils; -import com.jetbrains.edu.learning.PyEduPluginConfigurator; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class PyCCRunTestsConfigurationProducer extends RunConfigurationProducer { - protected PyCCRunTestsConfigurationProducer() { - super(PyCCRunTestsConfigurationType.getInstance()); - } - - @Override - protected boolean setupConfigurationFromContext(PyCCRunTestConfiguration configuration, - ConfigurationContext context, - Ref sourceElement) { - Project project = context.getProject(); - if (!CCUtils.isCourseCreator(project)) { - return false; - } - - String testsPath = getTestPath(context); - if (testsPath == null) { - return false; - } - VirtualFile testsFile = LocalFileSystem.getInstance().findFileByPath(testsPath); - if (testsFile == null) { - return false; - } - - String generatedName = generateName(testsFile, project); - if (generatedName == null) { - return false; - } - - configuration.setPathToTest(testsPath); - configuration.setName(generatedName); - return true; - } - - @Nullable - private static String generateName(@NotNull VirtualFile testsFile, @NotNull Project project) { - VirtualFile taskDir = StudyUtils.getTaskDir(testsFile); - if (taskDir == null) { - return null; - } - Task task = StudyUtils.getTask(project, taskDir); - if (task == null) { - return null; - } - String generatedName = task.getLesson().getName() + "/" + task.getName(); - if (task instanceof TaskWithSubtasks) { - int index = ((TaskWithSubtasks)task).getActiveSubtaskIndex() + 1; - generatedName += " " + index; - } - return generatedName; - } - - @Nullable - private static String getTestPath(@NotNull ConfigurationContext context) { - Location location = context.getLocation(); - if (location == null) { - return null; - } - VirtualFile file = location.getVirtualFile(); - if (file == null) { - return null; - } - VirtualFile taskDir = StudyUtils.getTaskDir(file); - if (taskDir == null) { - return null; - } - - Task task = StudyUtils.getTask(location.getProject(), taskDir); - if (task == null) { - return null; - } - String testsFileName = PyEduPluginConfigurator.getSubtaskTestsFileName(task instanceof TaskWithSubtasks ? - ((TaskWithSubtasks)task).getActiveSubtaskIndex() : 0); - String taskDirPath = FileUtil.toSystemDependentName(taskDir.getPath()); - String testsPath = taskDir.findChild(EduNames.SRC) != null ? - FileUtil.join(taskDirPath, EduNames.SRC, testsFileName) : - FileUtil.join(taskDirPath, testsFileName); - String filePath = FileUtil.toSystemDependentName(file.getPath()); - return filePath.equals(testsPath) ? testsPath : null; - } - - @Override - public boolean isConfigurationFromContext(PyCCRunTestConfiguration configuration, ConfigurationContext context) { - String path = getTestPath(context); - return path != null && path.equals(configuration.getPathToTest()); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationType.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationType.java deleted file mode 100644 index ac05cf1d8ca4..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationType.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jetbrains.edu.coursecreator.run; - -import com.intellij.execution.configurations.ConfigurationFactory; -import com.intellij.execution.configurations.ConfigurationType; -import com.intellij.execution.configurations.ConfigurationTypeUtil; -import com.intellij.icons.AllIcons; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; - -public class PyCCRunTestsConfigurationType implements ConfigurationType { - @Override - public String getDisplayName() { - return "Run Study Tests"; - } - - @Override - public String getConfigurationTypeDescription() { - return "Study Test Runner"; - } - - @Override - public Icon getIcon() { - return AllIcons.Actions.Lightning; - } - - @NotNull - @Override - public String getId() { - return "ccruntests"; - } - - @Override - public ConfigurationFactory[] getConfigurationFactories() { - return new ConfigurationFactory[]{new PyCCRunTestsConfigurationFactory(this)}; - } - - public static PyCCRunTestsConfigurationType getInstance() { - return ConfigurationTypeUtil.findConfigurationType(PyCCRunTestsConfigurationType.class); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingEditor.form b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingEditor.form deleted file mode 100644 index ee0b85508b1a..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingEditor.form +++ /dev/null @@ -1,34 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingsEditor.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingsEditor.java deleted file mode 100644 index 2a7b8806e880..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingsEditor.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.jetbrains.edu.coursecreator.run; - -import com.intellij.openapi.module.Module; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.options.SettingsEditor; -import com.intellij.openapi.project.Project; -import com.jetbrains.python.run.AbstractPyCommonOptionsForm; -import com.jetbrains.python.run.AbstractPythonRunConfiguration; -import com.jetbrains.python.run.PyCommonOptionsFormData; -import com.jetbrains.python.run.PyCommonOptionsFormFactory; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.util.List; - -public class PyCCSettingsEditor extends SettingsEditor { - private AbstractPyCommonOptionsForm myForm; - private Project myProject; - private JTextField myPathToTestFileField; - private JPanel myPanel; - - public PyCCSettingsEditor(Project project) { - myProject = project; - } - - @Override - protected void resetEditorFrom(@NotNull PyCCRunTestConfiguration s) { - AbstractPythonRunConfiguration.copyParams(s, myForm); - myPathToTestFileField.setText(s.getPathToTest()); - } - - @Override - protected void applyEditorTo(@NotNull PyCCRunTestConfiguration s) throws ConfigurationException { - AbstractPythonRunConfiguration.copyParams(myForm, s); - s.setPathToTest(myPathToTestFileField.getText()); - } - - @NotNull - @Override - protected JComponent createEditor() { - final JPanel mainPanel = new JPanel(new BorderLayout()); - mainPanel.add(myPanel, BorderLayout.NORTH); - myForm = createEnvPanel(); - mainPanel.add(myForm.getMainPanel(), BorderLayout.SOUTH); - return mainPanel; - } - - - @NotNull - private AbstractPyCommonOptionsForm createEnvPanel() { - return PyCommonOptionsFormFactory.getInstance().createForm(new PyCommonOptionsFormData() { - @Override - public Project getProject() { - return myProject; - } - - @Override - public List getValidModules() { - return AbstractPythonRunConfiguration.getValidModules(myProject); - } - - @Override - public boolean showConfigureInterpretersLink() { - return false; - } - }); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java deleted file mode 100644 index ae416c33993d..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java +++ /dev/null @@ -1,211 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.ide.IdeView; -import com.intellij.ide.fileTemplates.FileTemplate; -import com.intellij.ide.fileTemplates.FileTemplateManager; -import com.intellij.ide.fileTemplates.FileTemplateUtil; -import com.intellij.ide.util.DirectoryUtil; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.startup.StartupManager; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiManager; -import com.intellij.util.PathUtil; -import com.jetbrains.edu.learning.checker.PyStudyTaskChecker; -import com.jetbrains.edu.learning.checker.StudyTaskChecker; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; -import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator; -import com.jetbrains.python.PythonModuleTypeBase; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -public class PyEduPluginConfigurator implements EduPluginConfigurator { - public static final String PYTHON_3 = "3.x"; - public static final String PYTHON_2 = "2.x"; - private static final String TESTS_PY = "tests.py"; - private static final Logger LOG = Logger.getInstance(PyEduPluginConfigurator.class); - private static final String COURSE_NAME = "Introduction to Python.zip"; - private static final String TASK_PY = "task.py"; - private PyStudyDirectoryProjectGenerator myGenerator = new PyStudyDirectoryProjectGenerator(); - - @NotNull - @Override - public String getTestFileName() { - return TESTS_PY; - } - - @NotNull - @Override - public String getStepikDefaultLanguage() { - return "python3"; - } - - @Override - public PsiDirectory createTaskContent(@NotNull Project project, - @NotNull Task task, - @Nullable IdeView view, - @NotNull PsiDirectory parentDirectory, - @NotNull Course course) { - final Ref taskDirectory = new Ref<>(); - ApplicationManager.getApplication().runWriteAction(() -> { - String taskDirName = EduNames.TASK + task.getIndex(); - taskDirectory.set(DirectoryUtil.createSubdirectories(taskDirName, parentDirectory, "\\/")); - if (taskDirectory.isNull()) return; - - if (course.isAdaptive() && !task.getTaskFiles().isEmpty()) { - createTaskFilesFromText(task, taskDirectory.get()); - } - else { - createFilesFromTemplates(project, view, taskDirectory.get()); - } - }); - return taskDirectory.get(); - } - - private static void createTaskFilesFromText(@NotNull Task task, @Nullable PsiDirectory taskDirectory) { - if (taskDirectory == null) { - LOG.warn("Task directory is null. Cannot create task files"); - return; - } - - for (TaskFile file : task.getTaskFiles().values()) { - try { - StudyGenerator.createTaskFile(taskDirectory.getVirtualFile(), file); - } - catch (IOException e) { - LOG.warn(e.getMessage()); - } - } - } - - private static void createFilesFromTemplates(@NotNull Project project, - @Nullable IdeView view, - @NotNull PsiDirectory taskDirectory) { - StudyUtils.createFromTemplate(project, taskDirectory, TASK_PY, view, false); - StudyUtils.createFromTemplate(project, taskDirectory, TESTS_PY, view, false); - } - - @Override - public boolean excludeFromArchive(@NotNull String path) { - return path.contains("__pycache__") || path.endsWith(".pyc"); - } - - @Override - public boolean isTestFile(VirtualFile file) { - String name = file.getName(); - if (TESTS_PY.equals(name)) { - return true; - } - return name.contains(FileUtil.getNameWithoutExtension(TESTS_PY)) && name.contains(EduNames.SUBTASK_MARKER); - } - - @Override - public void createTestsForNewSubtask(@NotNull Project project, @NotNull TaskWithSubtasks task) { - VirtualFile taskDir = task.getTaskDir(project); - if (taskDir == null) { - return; - } - int nextSubtaskIndex = task.getLastSubtaskIndex() + 1; - String nextSubtaskTestsFileName = getSubtaskTestsFileName(nextSubtaskIndex); - ApplicationManager.getApplication().runWriteAction(() -> { - try { - PsiDirectory taskPsiDir = PsiManager.getInstance(project).findDirectory(taskDir); - FileTemplate testsTemplate = FileTemplateManager.getInstance(project).getInternalTemplate(TESTS_PY); - if (taskPsiDir == null || testsTemplate == null) { - return; - } - FileTemplateUtil.createFromTemplate(testsTemplate, nextSubtaskTestsFileName, null, taskPsiDir); - } - catch (Exception e) { - LOG.error(e); - } - }); - } - - @NotNull - public static String getSubtaskTestsFileName(int index) { - return index == 0 ? TESTS_PY : FileUtil.getNameWithoutExtension(TESTS_PY) + - EduNames.SUBTASK_MARKER + - index + "." + - FileUtilRt.getExtension(TESTS_PY); - } - - @NotNull - @Override - public String getDefaultHighlightingMode() { - return "python"; - } - - @NotNull - @Override - public String getLanguageScriptUrl() { - return getClass().getResource("/python.js").toExternalForm(); - } - - @Override - @NotNull - public StudyTaskChecker getPyCharmTaskChecker(@NotNull PyCharmTask task, @NotNull Project project) { - return new PyStudyTaskChecker(task, project); - } - - @Override - public List getBundledCoursePaths() { - @NonNls String jarPath = PathUtil.getJarPathForClass(PyEduPluginConfigurator.class); - - if (jarPath.endsWith(".jar")) { - final File jarFile = new File(jarPath); - - File pluginBaseDir = jarFile.getParentFile(); - return Collections.singletonList(new File(new File(pluginBaseDir, "courses"), COURSE_NAME).getPath()); - } - - return Collections.singletonList(new File(new File(jarPath, "courses"), COURSE_NAME).getPath()); - } - - @Override - public EduCourseProjectGenerator getEduCourseProjectGenerator() { - return myGenerator; - } - - public ModuleType getModuleType() { - return PythonModuleTypeBase.getInstance(); - } - - @Override - public void configureModule(@NotNull Module module) { - final Project project = module.getProject(); - StartupManager.getInstance(project).runWhenProjectIsInitialized(() -> { - final VirtualFile baseDir = project.getBaseDir(); - final String testHelper = EduNames.TEST_HELPER; - if (baseDir.findChild(testHelper) != null) return; - final FileTemplate template = FileTemplateManager.getInstance(project).getInternalTemplate("test_helper"); - final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(baseDir); - if (projectDir == null) return; - try { - FileTemplateUtil.createFromTemplate(template, testHelper, null, projectDir); - } - catch (Exception exception) { - LOG.error("Can't copy test_helper.py " + exception.getMessage()); - } - }); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java deleted file mode 100644 index 1bbbc4401cb6..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java +++ /dev/null @@ -1,389 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.execution.ExecutionException; -import com.intellij.facet.ui.FacetEditorValidator; -import com.intellij.facet.ui.FacetValidatorsManager; -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.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.project.DefaultProjectFactory; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl; -import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil; -import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; -import com.intellij.openapi.ui.LabeledComponent; -import com.intellij.openapi.util.IconLoader; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.platform.DirectoryProjectGenerator; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiManager; -import com.intellij.util.BooleanFunction; -import com.intellij.util.ui.UIUtil; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.RemoteCourse; -import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; -import com.jetbrains.edu.learning.newproject.EduCourseProjectGenerator; -import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicUser; -import com.jetbrains.edu.learning.ui.StudyNewProjectPanel; -import com.jetbrains.python.configuration.PyConfigurableInterpreterList; -import com.jetbrains.python.configuration.VirtualEnvProjectFilter; -import com.jetbrains.python.newProject.PyNewProjectSettings; -import com.jetbrains.python.newProject.PythonProjectGenerator; -import com.jetbrains.python.newProject.steps.PythonSdkChooserCombo; -import com.jetbrains.python.packaging.PyPackageManager; -import com.jetbrains.python.psi.LanguageLevel; -import com.jetbrains.python.remote.PyProjectSynchronizer; -import com.jetbrains.python.sdk.PyDetectedSdk; -import com.jetbrains.python.sdk.PySdkExtKt; -import com.jetbrains.python.sdk.PySdkListCellRenderer; -import com.jetbrains.python.sdk.PythonSdkType; -import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; -import icons.PythonIcons; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.TimeUnit; - -public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator - implements EduCourseProjectGenerator { - private static final Logger LOG = Logger.getInstance(PyStudyDirectoryProjectGenerator.class.getName()); - private final StudyProjectGenerator myGenerator; - private static final String NO_PYTHON_INTERPRETER = "Add python interpreter."; - private final boolean isLocal; - public ValidationResult myValidationResult = new ValidationResult("selected course is not valid"); - private PyNewProjectSettings mySettings = (PyNewProjectSettings)getProjectSettings(); - - @SuppressWarnings("unused") // used on startup - public PyStudyDirectoryProjectGenerator() { - this(false); - } - - public PyStudyDirectoryProjectGenerator(boolean isLocal) { - this.isLocal = isLocal; - myGenerator = new StudyProjectGenerator(); - myGenerator.addSettingsStateListener(new StudyProjectGenerator.SettingsListener() { - @Override - public void stateChanged(ValidationResult result) { - setValidationResult(result); - } - }); - } - - @Nls - @NotNull - @Override - public String getName() { - return "Educational"; - } - - @Nullable - @Override - public Icon getLogo() { - return PythonIcons.Python.Python_logo; - } - - - @Override - public void configureProject(@NotNull final Project project, @NotNull final VirtualFile baseDir, - @NotNull PyNewProjectSettings settings, - @NotNull Module module, - @Nullable PyProjectSynchronizer synchronizer) { - myGenerator.generateProject(project, baseDir); - createTestHelper(project, baseDir); - } - - private static void createTestHelper(@NotNull Project project, @NotNull VirtualFile baseDir) { - final String testHelper = EduNames.TEST_HELPER; - if (baseDir.findChild(testHelper) != null) return; - final FileTemplate template = FileTemplateManager.getInstance(project).getInternalTemplate("test_helper"); - final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(baseDir); - if (projectDir == null) return; - try { - FileTemplateUtil.createFromTemplate(template, testHelper, null, projectDir); - } - catch (Exception exception) { - LOG.error("Can't copy test_helper.py " + exception.getMessage()); - } - } - - @NotNull - @Override - public DirectoryProjectGenerator getDirectoryProjectGenerator() { - return this; - } - - public ValidationResult validate() { - final List sdks = PyConfigurableInterpreterList.getInstance(null).getAllPythonSdks(); - - ValidationResult validationResult; - if (sdks.isEmpty()) { - validationResult = new ValidationResult(NO_PYTHON_INTERPRETER); - } else { - validationResult = ValidationResult.OK; - } - - return validationResult; - } - - @NotNull - @Override - public ValidationResult validate(@NotNull String s) { - ValidationResult validationResult = validate(); - if (!validationResult.isOk()) { - myValidationResult = validationResult; - } - - return myValidationResult; - } - - @Override - public boolean beforeProjectGenerated() { - BooleanFunction function = beforeProjectGenerated(null); - return function != null && function.fun(this); - } - - @Override - public void afterProjectGenerated(@NotNull Project project) { - Sdk sdk = mySettings.getSdk(); - if (sdk == null) { - createAndAddVirtualEnv(project, mySettings); - sdk = mySettings.getSdk(); - } - SdkConfigurationUtil.setDirectoryProjectSdk(project, sdk); - } - - @Nullable - @Override - public LabeledComponent getLanguageSettingsComponent(@NotNull Course selectedCourse) { - final List sdks = PyConfigurableInterpreterList.getInstance(null).getAllPythonSdks(); - VirtualEnvProjectFilter.removeAllAssociated(sdks); - // by default we create new virtual env in project, we need to add this non-existing sdk to sdk list - ProjectJdkImpl fakeSdk = createFakeSdk(selectedCourse); - if (fakeSdk != null) { - sdks.add(0, fakeSdk); - } - PythonSdkChooserCombo combo = new PythonSdkChooserCombo(null, sdks, null, sdk -> true); - if (fakeSdk != null) { - patchRenderer(fakeSdk, combo); - combo.getComboBox().setSelectedItem(fakeSdk); - } - if (SystemInfo.isMac && !UIUtil.isUnderDarcula()) { - combo.putClientProperty("JButton.buttonType", null); - } - combo.setButtonIcon(PythonIcons.Python.InterpreterGear); - combo.addChangedListener(e -> { - Sdk selectedSdk = (Sdk)combo.getComboBox().getSelectedItem(); - mySettings.setSdk(selectedSdk == fakeSdk ? null : selectedSdk); - }); - return LabeledComponent.create(combo, "Interpreter", BorderLayout.WEST); - } - - @Nullable - private static ProjectJdkImpl createFakeSdk(@NotNull Course selectedCourse) { - String fakeSdkPath = getBaseSdk(selectedCourse); - if (fakeSdkPath == null) { - return null; - } - PythonSdkFlavor flavor = PythonSdkFlavor.getApplicableFlavors(false).get(0); - String prefix = flavor.getName() + " "; - String versionString = flavor.getVersionString(fakeSdkPath); - if (versionString == null || !versionString.contains(prefix)) { - return null; - } - String name = "new virtual env " + versionString.substring(prefix.length()); - return new ProjectJdkImpl(name, PythonSdkType.getInstance()); - } - - private static void patchRenderer(@NotNull ProjectJdkImpl fakeSdk, @NotNull PythonSdkChooserCombo combo) { - combo.getComboBox().setRenderer(new PySdkListCellRenderer(null) { - @Override - protected void customizeCellRenderer(@NotNull JList list, @Nullable Object value, int index, boolean selected, boolean hasFocus) { - super.customizeCellRenderer(list, value, index, selected, hasFocus); - if (value == fakeSdk) { - setIcon(IconLoader.getTransparentIcon(PythonIcons.Python.Virtualenv)); - } - } - }); - } - - public void setValidationResult(ValidationResult validationResult) { - myValidationResult = validationResult; - } - - @Nullable - @Override - public JPanel extendBasePanel() throws ProcessCanceledException { - StudyNewProjectPanel mySettingsPanel = new StudyNewProjectPanel(myGenerator, isLocal); - mySettingsPanel.registerValidators(new FacetValidatorsManager() { - public void registerValidator(FacetEditorValidator validator, JComponent... componentsToWatch) { - throw new UnsupportedOperationException(); - } - - public void validate() { - ApplicationManager.getApplication().invokeLater(() -> fireStateChanged()); - } - }); - - addErrorLabelMouseListener(new MouseAdapter() { - private boolean isCourseAdaptiveAndNotLogged() { - Course course = myGenerator.getSelectedCourse(); - return course != null && course.isAdaptive() && !myGenerator.isLoggedIn(); - } - - @Override - public void mouseClicked(MouseEvent e) { - if (isCourseAdaptiveAndNotLogged()) { - StudySettings studySettings = StudySettings.getInstance(); - StepicUser oldUser = studySettings.getUser(); - - EduStepicConnector.doAuthorize(() -> mySettingsPanel.showLoginDialog()); - - ProgressManager.getInstance() - .runProcessWithProgressSynchronously(() -> { - ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - StepicUser user = StudyUtils.execCancelable(() -> { - StepicUser newUser = studySettings.getUser(); - while (newUser == null || newUser.equals(oldUser)) { - TimeUnit.MILLISECONDS.sleep(500); - newUser = studySettings.getUser(); - } - myGenerator.setEnrolledCoursesIds(EduAdaptiveStepicConnector.getEnrolledCoursesIds(newUser)); - - - return newUser; - }); - - if (user != null) { - mySettingsPanel.setOK(); - } - }, "Authorizing", - true, - DefaultProjectFactory.getInstance().getDefaultProject()); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - if (isCourseAdaptiveAndNotLogged()) { - e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - } - } - - @Override - public void mouseExited(MouseEvent e) { - if (isCourseAdaptiveAndNotLogged()) { - e.getComponent().setCursor(Cursor.getDefaultCursor()); - } - } - }); - - return mySettingsPanel; - } - - public void setCourse(@NotNull Course course) { - myGenerator.setSelectedCourse(course); - } - - public StudyProjectGenerator getGenerator() { - return myGenerator; - } - - @Nullable - @Override - public BooleanFunction beforeProjectGenerated(@Nullable Sdk sdk) { - return generator -> { - final List enrolledCoursesIds = myGenerator.getEnrolledCoursesIds(); - final Course course = myGenerator.getSelectedCourse(); - if (course == null || !(course instanceof RemoteCourse)) return true; - if (((RemoteCourse)course).getId() > 0 && !enrolledCoursesIds.contains(((RemoteCourse)course).getId())) { - ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { - ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - return StudyUtils.execCancelable(() -> EduStepicConnector.enrollToCourse(((RemoteCourse)course).getId(), - StudySettings.getInstance().getUser())); - }, "Creating Course", true, ProjectManager.getInstance().getDefaultProject()); - } - return true; - }; - } - - public void createAndAddVirtualEnv(Project project, PyNewProjectSettings settings) { - final ProjectSdksModel model = PyConfigurableInterpreterList.getInstance(project).getModel(); - Course course = StudyTaskManager.getInstance(project).getCourse(); - if (course == null) { - return; - } - final String baseSdkPath = getBaseSdk(course); - if (baseSdkPath != null) { - final PyDetectedSdk baseSdk = new PyDetectedSdk(baseSdkPath); - final String virtualEnvPath = project.getBasePath() + "/.idea/VirtualEnvironment"; - final Sdk sdk = PySdkExtKt.createSdkByGenerateTask(new Task.WithResult(project, - "Creating Virtual Environment", - false) { - @Override - protected String compute(@NotNull ProgressIndicator indicator) throws ExecutionException { - indicator.setIndeterminate(true); - final PyPackageManager packageManager = PyPackageManager.getInstance(baseSdk); - return packageManager.createVirtualEnv(virtualEnvPath, false); - } - }, Arrays.asList(model.getSdks()), baseSdk, project.getBasePath()); - if (sdk == null) { - LOG.warn("Failed to create virtual env in " + virtualEnvPath); - return; - } - settings.setSdk(sdk); - SdkConfigurationUtil.addSdk(sdk); - PySdkExtKt.associateWithProject(sdk, project, false); - } - } - - private static String getBaseSdk(@NotNull final Course course) { - LanguageLevel baseLevel = LanguageLevel.PYTHON36; - final String version = course.getLanguageVersion(); - if (PyEduPluginConfigurator.PYTHON_2.equals(version)) { - baseLevel = LanguageLevel.PYTHON27; - } - else if (PyEduPluginConfigurator.PYTHON_3.equals(version)) { - baseLevel = LanguageLevel.PYTHON36; - } - else if (version != null) { - baseLevel = LanguageLevel.fromPythonVersion(version); - } - final PythonSdkFlavor flavor = PythonSdkFlavor.getApplicableFlavors(false).get(0); - String baseSdk = null; - final Collection baseSdks = flavor.suggestHomePaths(); - for (String sdk : baseSdks) { - final String versionString = flavor.getVersionString(sdk); - final String prefix = flavor.getName() + " "; - if (versionString != null && versionString.startsWith(prefix)) { - final LanguageLevel level = LanguageLevel.fromPythonVersion(versionString.substring(prefix.length())); - if (level.isAtLeast(baseLevel)) { - baseSdk = sdk; - break; - } - } - } - return baseSdk != null ? baseSdk : baseSdks.iterator().next(); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyImportResolver.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyImportResolver.java deleted file mode 100644 index 430015dc1e78..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyImportResolver.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.util.QualifiedName; -import com.jetbrains.python.psi.impl.PyImportResolver; -import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext; -import org.jetbrains.annotations.Nullable; - -public class PyStudyImportResolver implements PyImportResolver { - @Nullable - public PsiElement resolveImportReference(QualifiedName name, PyQualifiedNameResolveContext context, boolean withRoots) { - if (StudyTaskManager.getInstance(context.getProject()).getCourse() == null) { - return null; - } - final String nameString = name.toString(); - PsiFile containingFile = context.getFootholdFile(); - if (containingFile == null) return null; - - final PsiDirectory directory = containingFile.getContainingDirectory(); - if (directory == null) return null; - final PsiFile file = directory.findFile(nameString + ".py"); - if (file != null) { - return file; - } - - return null; - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInitialConfigurator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInitialConfigurator.java deleted file mode 100644 index fb87f4f59b8a..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInitialConfigurator.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.project.ex.ProjectManagerEx; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.util.messages.MessageBus; -import org.jetbrains.annotations.NonNls; - -import java.io.File; - -@SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "UtilityClassWithPublicConstructor"}) -public class PyStudyInitialConfigurator implements ApplicationComponent { - @NonNls private static final String CONFIGURED_V40 = "StudyPyCharm.InitialConfiguration40"; - - /** - * @noinspection UnusedParameters - */ - public PyStudyInitialConfigurator(MessageBus bus, - CodeInsightSettings codeInsightSettings, - final PropertiesComponent propertiesComponent, - FileTypeManager fileTypeManager, - final ProjectManagerEx projectManager) { - if (!propertiesComponent.getBoolean(CONFIGURED_V40)) { - final File courses = new File(PathManager.getConfigPath(), "courses"); - FileUtil.delete(courses); - propertiesComponent.setValue(CONFIGURED_V40, "true"); - } - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInterpreterInspection.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInterpreterInspection.java deleted file mode 100644 index 001332ea3bf0..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInterpreterInspection.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2000-2016 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; - -import com.intellij.codeInspection.LocalInspectionToolSession; -import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.ProblemDescriptor; -import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtilCore; -import com.intellij.openapi.options.ShowSettingsUtil; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.psi.PsiElementVisitor; -import com.intellij.util.PlatformUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.python.inspections.PyInspection; -import com.jetbrains.python.inspections.PyInspectionVisitor; -import com.jetbrains.python.psi.LanguageLevel; -import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.sdk.PythonSdkType; -import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; -import org.jetbrains.annotations.Nls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class PyStudyInterpreterInspection extends PyInspection { - - @Nls - @NotNull - public String getDisplayName() { - return "Wrong python interpreter"; - } - - @NotNull - @Override - public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, - final boolean isOnTheFly, - @NotNull final LocalInspectionToolSession session) { - return new Visitor(holder, session); - } - - public static class Visitor extends PyInspectionVisitor { - - public Visitor(@Nullable ProblemsHolder holder, - @NotNull LocalInspectionToolSession session) { - super(holder, session); - } - - @Override - public void visitPyFile(PyFile node) { - super.visitPyFile(node); - if (PlatformUtils.isPyCharmEducational()) { - final Course course = StudyTaskManager.getInstance(node.getProject()).getCourse(); - if (course == null) return; - - final Module module = ModuleUtilCore.findModuleForPsiElement(node); - if (module != null) { - final Sdk sdk = PythonSdkType.findPythonSdk(module); - if (sdk == null) return; - final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk); - if (flavor == null) return; - final String versionString = flavor.getVersionString(sdk.getHomePath()); - if (versionString == null) return; - final String prefix = flavor.getName() + " "; - if (!versionString.startsWith(prefix)) { - return; - } - final LanguageLevel projectLanguageLevel = LanguageLevel.fromPythonVersion(versionString.substring(prefix.length())); - - final String version = course.getLanguageVersion(); - if (PyEduPluginConfigurator.PYTHON_2.equals(version)) { - if (projectLanguageLevel.isPy3K()) { - registerProblem(node, "Course is available for Python 2, but Python 3 is selected as project interpreter", new ConfigureInterpreterFix()); - } - } - else if (PyEduPluginConfigurator.PYTHON_3.equals(version)) { - if (!projectLanguageLevel.isPy3K()) { - registerProblem(node, "Course is available for Python 3, but Python 2 is selected as project interpreter", new ConfigureInterpreterFix()); - } - } - else if (version != null) { - final LanguageLevel level = LanguageLevel.fromPythonVersion(version); - if (!level.equals(projectLanguageLevel)) { - registerProblem(node, "Course is available for Python " + level.toString() + ", but Python " + projectLanguageLevel.toString() - + " is selected as project interpreter", new ConfigureInterpreterFix()); - } - } - } - } - } - } - - private static class ConfigureInterpreterFix implements LocalQuickFix { - - @NotNull - @Override - public String getFamilyName() { - return "Configure Python Interpreter"; - } - - @Override - public boolean startInWriteAction() { - return false; - } - - @Override - public void applyFix(@NotNull final Project project, @NotNull ProblemDescriptor descriptor) { - ShowSettingsUtil.getInstance().showSettingsDialog(project, "Project Interpreter"); - } - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyLanguageLevelInspection.kt b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyLanguageLevelInspection.kt deleted file mode 100644 index 474a169c9f49..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyLanguageLevelInspection.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.jetbrains.edu.learning - -import com.intellij.codeInspection.LocalInspectionToolSession -import com.intellij.codeInspection.LocalQuickFix -import com.intellij.codeInspection.ProblemDescriptor -import com.intellij.codeInspection.ProblemsHolder -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.module.ModuleUtilCore -import com.intellij.openapi.options.ShowSettingsUtil -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiElementVisitor -import com.intellij.psi.util.PsiUtilCore -import com.jetbrains.edu.learning.courseFormat.Course -import com.jetbrains.python.inspections.PyInspection -import com.jetbrains.python.inspections.PyInspectionVisitor -import com.jetbrains.python.psi.LanguageLevel -import com.jetbrains.python.psi.PyFile -import com.jetbrains.python.sdk.PythonSdkType -import org.jetbrains.annotations.Nls - -class PyStudyLanguageLevelInspection : PyInspection() { - @Nls - override fun getDisplayName(): String { - return "Unsupported language level for a course" - } - - override fun buildVisitor(holder: ProblemsHolder, - isOnTheFly: Boolean, - session: LocalInspectionToolSession): PsiElementVisitor { - return Visitor(holder, session) - } - - class Visitor(holder: ProblemsHolder?, - session: LocalInspectionToolSession) : PyInspectionVisitor(holder, session) { - - override fun visitPyFile(node: PyFile) { - super.visitPyFile(node) - val module = ModuleUtilCore.findModuleForPsiElement(node) - val virtualFile = PsiUtilCore.getVirtualFile(node) - if (module != null && virtualFile != null) { - val sdk = PythonSdkType.findPythonSdk(module) - if (sdk != null) { - val projectLanguageLevel = PythonSdkType.getLanguageLevelForSdk(sdk) - val project = node.project - val course = StudyTaskManager.getInstance(project).course ?: return - checkIfLanguageLevelSupported(course, projectLanguageLevel, node) - } - } - } - - fun checkIfLanguageLevelSupported(course: Course, languageLevel: LanguageLevel, node: PyFile) { - if (course.isAdaptive) { - if(!languageLevel.isPy3K) { - registerProblem(node, "Adaptive courses support Python 3 only", ConfigureInterpreterFix()) - } - } - } - } -} - - -private class ConfigureInterpreterFix : LocalQuickFix { - override fun getName(): String { - return "Configure python interpreter" - } - - override fun getFamilyName(): String { - return "Configure python interpreter" - } - - override fun applyFix(project: Project, descriptor: ProblemDescriptor) { - ApplicationManager.getApplication() - .invokeLater { ShowSettingsUtil.getInstance().showSettingsDialog(project, "Project Interpreter") } - } -} - diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyShowTutorial.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyShowTutorial.java deleted file mode 100644 index 101069b82a3c..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyShowTutorial.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.ide.util.PropertiesComponent; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationListener; -import com.intellij.notification.NotificationType; -import com.intellij.notification.Notifications; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.AbstractProjectComponent; -import com.intellij.openapi.project.DumbAwareRunnable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.startup.StartupManager; - -public class PyStudyShowTutorial extends AbstractProjectComponent { - - private static final String POPUP_SHOWN = "StudyShowPopup"; - private final Project myProject; - - protected PyStudyShowTutorial(Project project) { - super(project); - myProject = project; - } - - @Override - public void projectOpened() { - if (PropertiesComponent.getInstance().isValueSet(POPUP_SHOWN)) { - return; - } - ApplicationManager.getApplication().invokeLater((DumbAwareRunnable)() -> ApplicationManager.getApplication().runWriteAction( - (DumbAwareRunnable)() -> { - final String content = "If you'd like to learn more about PyCharm Edu, " + - "click here to watch a tutorial"; - final Notification notification = new Notification("Watch Tutorials!", "", content, NotificationType.INFORMATION, - new NotificationListener.UrlOpeningListener(true)); - StartupManager.getInstance(myProject).registerPostStartupActivity(() -> { - Notifications.Bus.notify(notification); - PropertiesComponent.getInstance().setValue(POPUP_SHOWN, true); - }); - })); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyWatchTutorialAction.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyWatchTutorialAction.java deleted file mode 100644 index af44af7d4689..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/actions/PyStudyWatchTutorialAction.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.jetbrains.edu.learning.actions; - -import com.intellij.ide.BrowserUtil; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.project.DumbAwareAction; -import org.jetbrains.annotations.NotNull; - -public class PyStudyWatchTutorialAction extends DumbAwareAction { - public PyStudyWatchTutorialAction() { - super("Learn more about PyCharm Edu", null, null); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - BrowserUtil.browse("https://www.jetbrains.com/pycharm-edu/quickstart/"); - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudySmartChecker.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudySmartChecker.java deleted file mode 100644 index dd7ad5143b2c..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudySmartChecker.java +++ /dev/null @@ -1,144 +0,0 @@ -package com.jetbrains.edu.learning.checker; - -import com.intellij.execution.ExecutionException; -import com.intellij.execution.process.CapturingProcessHandler; -import com.intellij.execution.process.ProcessOutput; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -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.Course; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; - -class PyStudySmartChecker { - private PyStudySmartChecker() { - - } - - private static final Logger LOG = Logger.getInstance(PyStudySmartChecker.class); - - private static void smartCheck(@NotNull final AnswerPlaceholder placeholder, - @NotNull final Project project, - @NotNull final VirtualFile answerFile, - @NotNull final TaskFile answerTaskFile, - @NotNull final TaskFile usersTaskFile, - @NotNull final PyStudyTestRunner testRunner, - @NotNull final VirtualFile virtualFile, - @NotNull final Document usersDocument) { - VirtualFile fileWindows = null; - VirtualFile windowCopy = null; - try { - final int index = placeholder.getIndex(); - String windowCopyName = answerFile.getNameWithoutExtension() + index + EduNames.WINDOW_POSTFIX + answerFile.getExtension(); - windowCopy = answerFile.copy(project, answerFile.getParent(), windowCopyName); - final FileDocumentManager documentManager = FileDocumentManager.getInstance(); - final Document windowDocument = documentManager.getDocument(windowCopy); - if (windowDocument != null) { - TaskFile windowTaskFile = answerTaskFile.getTask().copy().getTaskFile(StudyUtils.pathRelativeToTask(virtualFile)); - if (windowTaskFile == null) { - return; - } - EduDocumentListener listener = new EduDocumentListener(windowTaskFile); - windowDocument.addDocumentListener(listener); - int start = placeholder.getOffset(); - int end = start + placeholder.getRealLength(); - final AnswerPlaceholder userAnswerPlaceholder = usersTaskFile.getAnswerPlaceholders().get(placeholder.getIndex()); - int userStart = userAnswerPlaceholder.getOffset(); - int userEnd = userStart + userAnswerPlaceholder.getRealLength(); - String text = usersDocument.getText(new TextRange(userStart, userEnd)); - windowDocument.replaceString(start, end, text); - ApplicationManager.getApplication().runWriteAction(() -> documentManager.saveDocument(windowDocument)); - fileWindows = EduUtils.flushWindows(windowTaskFile, windowCopy); - Process smartTestProcess = testRunner.createCheckProcess(project, windowCopy.getPath()); - final CapturingProcessHandler handler = new CapturingProcessHandler(smartTestProcess, null, windowCopy.getPath()); - final ProcessOutput output = handler.runProcess(); - 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); - } - } - } - catch (ExecutionException | IOException e) { - LOG.error(e); - } - finally { - StudyUtils.deleteFile(windowCopy); - StudyUtils.deleteFile(fileWindows); - } - } - - static void runSmartTestProcess(@NotNull final VirtualFile taskDir, - @NotNull final PyStudyTestRunner testRunner, - @NotNull final String taskFileName, - @NotNull final TaskFile taskFile, - @NotNull final Project project) { - final VirtualFile virtualFile = taskDir.findFileByRelativePath(taskFileName); - if (virtualFile == null) { - return; - } - Pair pair = getCopyWithAnswers(taskDir, virtualFile, taskFile); - if (pair == null) { - return; - } - VirtualFile answerFile = pair.getFirst(); - TaskFile answerTaskFile = pair.getSecond(); - try { - for (final AnswerPlaceholder answerPlaceholder : answerTaskFile.getActivePlaceholders()) { - final Document document = FileDocumentManager.getInstance().getDocument(virtualFile); - if (document == null) { - continue; - } - smartCheck(answerPlaceholder, project, answerFile, answerTaskFile, taskFile, testRunner, - virtualFile, document); - } - } - finally { - StudyUtils.deleteFile(answerFile); - } - } - - static Pair getCopyWithAnswers(@NotNull final VirtualFile taskDir, - @NotNull final VirtualFile file, - @NotNull final TaskFile source) { - try { - VirtualFile answerFile = file.copy(taskDir, taskDir, file.getNameWithoutExtension() + EduNames.ANSWERS_POSTFIX + "." + file.getExtension()); - final FileDocumentManager documentManager = FileDocumentManager.getInstance(); - final Document document = documentManager.getDocument(answerFile); - if (document != null) { - TaskFile answerTaskFile = source.getTask().copy().getTaskFile(StudyUtils.pathRelativeToTask(file)); - if (answerTaskFile == null) { - return null; - } - EduDocumentListener listener = new EduDocumentListener(answerTaskFile); - document.addDocumentListener(listener); - for (AnswerPlaceholder answerPlaceholder : answerTaskFile.getActivePlaceholders()) { - final int start = answerPlaceholder.getOffset(); - final int end = start + answerPlaceholder.getRealLength(); - final String text = answerPlaceholder.getPossibleAnswer(); - document.replaceString(start, end, text); - } - ApplicationManager.getApplication().runWriteAction(() -> documentManager.saveDocument(document)); - return Pair.create(answerFile, answerTaskFile); - } - } - catch (IOException e) { - LOG.error(e); - } - return null; - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudyTaskChecker.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudyTaskChecker.java deleted file mode 100644 index ab739a7d05c1..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudyTaskChecker.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.jetbrains.edu.learning.checker; - -import com.intellij.execution.ExecutionException; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.actions.StudyCheckAction; -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.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicUser; -import one.util.streamex.EntryStream; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; -import java.util.concurrent.CountDownLatch; - -public class PyStudyTaskChecker extends StudyTaskChecker { - private static final Logger LOG = Logger.getInstance(PyStudyTaskChecker.class); - - public PyStudyTaskChecker(PyCharmTask task, Project project) { - super(task, project); - } - - @Override - public StudyCheckResult check() { - VirtualFile taskDir = myTask.getTaskDir(myProject); - if (taskDir == null) { - LOG.info("taskDir is null for task " + myTask.getName()); - return new StudyCheckResult(StudyStatus.Unchecked, "Task is broken"); - } - CountDownLatch latch = new CountDownLatch(1); - ApplicationManager.getApplication() - .invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { - StudyCheckUtils.flushWindows(myTask, taskDir); - latch.countDown(); - })); - final PyStudyTestRunner testRunner = new PyStudyTestRunner(myTask, taskDir); - try { - final VirtualFile fileToCheck = getTaskVirtualFile(myTask, taskDir); - if (fileToCheck != null) { - //otherwise answer placeholders might have been not flushed yet - latch.await(); - Process testProcess = testRunner.createCheckProcess(myProject, fileToCheck.getPath()); - StudyTestsOutputParser.TestsOutput output = - StudyCheckUtils - .getTestOutput(testProcess, testRunner.getCommandLine().getCommandLineString(), myTask.getLesson().getCourse().isAdaptive()); - return new StudyCheckResult(output.isSuccess() ? StudyStatus.Solved : StudyStatus.Failed, output.getMessage()); - } - } - catch (ExecutionException | InterruptedException e) { - LOG.error(e); - } - return new StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH); - } - - @Override - public void clearState() { - ApplicationManager.getApplication().invokeLater(() -> { - StudyCheckUtils.drawAllPlaceholders(myProject, myTask); - VirtualFile taskDir = myTask.getTaskDir(myProject); - if (taskDir != null) { - EduUtils.deleteWindowDescriptions(myTask, taskDir); - } - }); - } - - @Override - public void onTaskFailed(@NotNull String message) { - super.onTaskFailed(message); - ApplicationManager.getApplication().invokeLater(() -> { - VirtualFile taskDir = myTask.getTaskDir(myProject); - if (taskDir == null) return; - for (Map.Entry entry : myTask.getTaskFiles().entrySet()) { - final String name = entry.getKey(); - final TaskFile taskFile = entry.getValue(); - if (taskFile.getActivePlaceholders().size() < 2) { - continue; - } - final Course course = myTask.getLesson().getCourse(); - if (course != null && EduNames.STUDY.equals(course.getCourseMode())) { - CommandProcessor.getInstance().runUndoTransparentAction( - () -> ApplicationManager.getApplication().runWriteAction( - () -> PyStudySmartChecker.runSmartTestProcess(taskDir, new PyStudyTestRunner(myTask, taskDir), name, taskFile, myProject))); - } - } - StudyCheckUtils.navigateToFailedPlaceholder(new StudyState(StudyUtils.getSelectedStudyEditor(myProject)), myTask, taskDir, myProject); - }); - } - - @Nullable - private static VirtualFile getTaskVirtualFile(@NotNull final Task task, - @NotNull final VirtualFile taskDir) { - - Map fileMap = - EntryStream.of(task.getTaskFiles()).invert().mapValues(name -> taskDir.findFileByRelativePath(name)).nonNullValues().toMap(); - Map.Entry entry = EntryStream.of(fileMap).findAny(e -> !e.getKey().getActivePlaceholders().isEmpty()) - .orElse(fileMap.entrySet().stream().findFirst().orElse(null)); - return entry == null ? null : entry.getValue(); - } - - @Override - public StudyCheckResult checkOnRemote(@Nullable StepicUser user) { - StudyCheckResult result = check(); - final Course course = StudyTaskManager.getInstance(myProject).getCourse(); - StudyStatus status = result.getStatus(); - if (user != null && course != null && EduNames.STUDY.equals(course.getCourseMode()) && status != StudyStatus.Unchecked) { - EduStepicConnector.postSolution(myTask, status == StudyStatus.Solved, myProject); - } - return result; - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudyTestRunner.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudyTestRunner.java deleted file mode 100644 index cbcb35c145ae..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/checker/PyStudyTestRunner.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.jetbrains.edu.learning.checker; - -import com.intellij.execution.ExecutionException; -import com.intellij.execution.configurations.GeneralCommandLine; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.PyEduPluginConfigurator; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; -import com.jetbrains.python.sdk.PythonSdkType; -import org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.util.Map; - -class PyStudyTestRunner { - private static final String PYTHONPATH = "PYTHONPATH"; - @NotNull private final Task myTask; - @NotNull private final VirtualFile myTaskDir; - private GeneralCommandLine myCommandLine; - - PyStudyTestRunner(@NotNull final Task task, @NotNull final VirtualFile taskDir) { - myTask = task; - myTaskDir = taskDir; - } - - Process createCheckProcess(@NotNull final Project project, @NotNull final String executablePath) throws ExecutionException { - final Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]); - PyEduPluginConfigurator configurator = new PyEduPluginConfigurator(); - String testsFileName = configurator.getTestFileName(); - if (myTask instanceof TaskWithSubtasks) { - testsFileName = FileUtil.getNameWithoutExtension(testsFileName); - int index = ((TaskWithSubtasks)myTask).getActiveSubtaskIndex(); - testsFileName += EduNames.SUBTASK_MARKER + index + "." + FileUtilRt.getExtension(configurator.getTestFileName()); - } - final File testRunner = new File(myTaskDir.getPath(), testsFileName); - myCommandLine = new GeneralCommandLine(); - myCommandLine.withWorkDirectory(myTaskDir.getPath()); - final Map env = myCommandLine.getEnvironment(); - - final VirtualFile courseDir = project.getBaseDir(); - if (courseDir != null) { - env.put(PYTHONPATH, courseDir.getPath()); - } - if (sdk != null) { - String pythonPath = sdk.getHomePath(); - if (pythonPath != null) { - myCommandLine.setExePath(pythonPath); - myCommandLine.addParameter(testRunner.getPath()); - myCommandLine.addParameter(FileUtil.toSystemDependentName(executablePath)); - return myCommandLine.createProcess(); - } - } - return null; - } - - GeneralCommandLine getCommandLine() { - return myCommandLine; - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/highlighting/PyStudyInspectionExtension.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/highlighting/PyStudyInspectionExtension.java deleted file mode 100644 index 86c67130a773..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/highlighting/PyStudyInspectionExtension.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jetbrains.edu.learning.highlighting; - -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiReference; -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.python.inspections.PyInspectionExtension; -import com.jetbrains.python.psi.PyElement; -import com.jetbrains.python.psi.PyImportStatementBase; -import com.jetbrains.python.psi.types.TypeEvalContext; -import org.jetbrains.annotations.NotNull; - -public class PyStudyInspectionExtension extends PyInspectionExtension { - - @Override - public boolean ignoreUnresolvedReference(@NotNull PyElement element, @NotNull PsiReference reference, @NotNull TypeEvalContext context) { - final PsiFile file = element.getContainingFile(); - final Project project = file.getProject(); - - if (StudyTaskManager.getInstance(project).getCourse() == null) { - return false; - } - TaskFile taskFile = StudyUtils.getTaskFile(project, file.getVirtualFile()); - if (taskFile == null || taskFile.isUserCreated() || taskFile.isHighlightErrors()) { - return false; - } - if (PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class) != null) { - return false; - } - return true; - } - -} diff --git a/python/educational-python/build.gradle b/python/educational-python/build.gradle deleted file mode 100644 index 4ac2ec7f1cd0..000000000000 --- a/python/educational-python/build.gradle +++ /dev/null @@ -1,119 +0,0 @@ -import org.apache.tools.ant.filters.ReplaceTokens - -buildscript { - ext.kotlin_version = '1.0.6' - repositories { - mavenCentral() - maven { - url "https://oss.sonatype.org/content/repositories/snapshots/" - } - maven { - url 'http://dl.bintray.com/jetbrains/intellij-plugin-service' - } - maven { - url "https://plugins.gradle.org/m2/" - } - } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.intellij.plugins:gradle-intellij-plugin:0.2.0-SNAPSHOT" - } -} - -subprojects { - apply plugin: 'java' - apply plugin: 'kotlin' - apply plugin: "org.jetbrains.intellij" - - tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } - targetCompatibility = '1.8' - sourceCompatibility = '1.8' - sourceSets { - main { - java.srcDirs 'src', '../src', '../gen' - kotlin.srcDirs 'src' - resources.srcDirs 'resources', '../resources' - } - test { - java.srcDir 'test' - } - } - - processResources { - filesNotMatching("**/*.png") { - it.filter(ReplaceTokens, tokens: [ - 'VERSION' : version.toString(), - 'BUILD-NUMBER': buildNumber, - ]) - } - } - - repositories { - ivy { - ivyPattern 'http://buildserver.labs.intellij.net/guestAuth/repository/download/ijplatform_master_PyCharm_Installers/lastSuccessful/teamcity-ivy.xml' - artifactPattern 'http://buildserver.labs.intellij.net/guestAuth/repository/download/ijplatform_master_PyCharm_Installers/lastSuccessful/[artifact].[ext]' - } - } - - configurations { - pycharm - } - - dependencies { - pycharm ('org:ijplatform_master_PyCharm_Installers:lastSuccessful') - compileOnly fileTree(dir: rootProject.projectDir.toString() + "/gradleBuild/pycharm", include: ['**/*.jar']) - } - - task extractPyCharm(type: Copy) { - from zipTree(configurations.pycharm.filter{dep -> dep.name.startsWith("pycharmPC-") && dep.name.endsWith(".zip")}[0]) - into rootProject.projectDir.toString() + "/gradleBuild/pycharm" - include "**/pycharm*.jar" - } - - intellij { - version ideaVersion - updateSinceUntilBuild Boolean.valueOf(updateBuildNumber) - downloadSources Boolean.valueOf(downloadIdeaSources) - sandboxDirectory = new File(rootProject.projectDir, "gradleBuild/idea-sandbox") - } - - buildDir = new File(rootProject.projectDir, "gradleBuild/" + project.name) -} - -project(':Edu-Python') { - sourceSets { - main { - resources.srcDirs '../resources' - } - } - - dependencies { - compile project(':educational-core') { - dependencies { - compile fileTree(dir: 'lib', include: ['*.jar']) - } - } - } - - compileJava.dependsOn(extractPyCharm) - compileKotlin.dependsOn(extractPyCharm) - - intellij { - pluginName 'Edu-Python' - - if (project.hasProperty('ideaPath')) { - localPath ideaPath - } else { - if (project.hasProperty('ideaVersion')) { - version ideaVersion - } - } - } - - afterEvaluate { - tasks.getByName('buildPlugin') { - archiveName = "$intellij.pluginName-$version.zip" - } - } - -} \ No newline at end of file diff --git a/python/educational-python/build/plugin-list.txt b/python/educational-python/build/plugin-list.txt index 85e94c071495..14d77d2a4df9 100644 --- a/python/educational-python/build/plugin-list.txt +++ b/python/educational-python/build/plugin-list.txt @@ -2,7 +2,5 @@ svn4idea git4idea remote-servers-git github -educational-core -Edu-Python hg4idea ipnb \ No newline at end of file diff --git a/python/educational-python/educational-python.iml b/python/educational-python/educational-python.iml index 3d354ed20918..c5356758b650 100644 --- a/python/educational-python/educational-python.iml +++ b/python/educational-python/educational-python.iml @@ -26,6 +26,6 @@ - + \ No newline at end of file diff --git a/python/educational-python/gradle.properties b/python/educational-python/gradle.properties deleted file mode 100644 index 0f868ddf89f0..000000000000 --- a/python/educational-python/gradle.properties +++ /dev/null @@ -1,6 +0,0 @@ -ideaVersion IC-171-SNAPSHOT -downloadIdeaSources false -version 4.0 -buildNumber 171 -javaVersion 1.8 -updateBuildNumber false \ No newline at end of file diff --git a/python/educational-python/gradle/wrapper/gradle-wrapper.jar b/python/educational-python/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 941144813d241db74e1bf25b6804c679fbe7f0a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53636 zcmafaW0a=B^559DjdyI@wr$%scWm3Xy<^+Pj_sKpY&N+!|K#4>Bz;ajPk*RBjZ;RV75EK-Uv!Ig%(BB5~-#>pF^k0$_Qx&35mhPeng zP5V`%P1S)(UiPcRczm!G=UnT-`Q91$f1A+!-&O|pcR~kei+@?vzw^NUlgGl@$kf*C z|H+E_udE)q?&-+Q}NKDTwWGi9|EhSaen+=P&UpS2Bbjf?dM==%4Q|xN(%II>dI89;ro*BL4Red4p@gCHx)jxu84C!g zjsX&OW)$y=#n_cmkmSKx8wB`wsWLl2JqjeaVk7bSmJ^1~lfVg!V?hu`#16r`(c%03 z+bNIihOMIg6#&P-M=bjP*`tb=i>sNPqO-%_!*aDUbNSoz^b&G&wKTJLwK6esb#VU2 zA(X1vIiLt3`C|Yg#ug4M4Qo?3SG`q_qZ}3taiC*=Kr_iz$;k@X8G%~Vd6+sRKGZ)& z+p*q5z7@wb3#JkQquvh9UhzIo^YV1R9-Xe;0!?~alf(u?!-9j_P;Ij}#>Jwst7xv? z;G^nv*pMKM4YURMz)fK4?^o)Dcc}21N-htU8ERJf1bHs;abY~r3A|7luMI)GB6dDK z`J>5Jv|%#U5I&KT%fFbdBP)B6kleNyTvxS0rL65!r@*aV5+OC6JOWULy|fU`rtGA4 zpTf41dqh+{7_Pwm$Fs8^Vb!tHbcC-}I`skBCK;FzaJce~-$4Pt?1@r%_$rO}`9UT7 zSX5*>iy%>Xc8mbiQl^ZEgLSr%8hHc?Cm_^TR2a;fB{(joOtfvO7b)Do$8Sl9;dvVr zgJnGKAUpQ0O~(W`21R%m@d)wFTZN=-_R3{~N+V)|9y!dZ2Gsh{a2TeDzb zE)?K2{8YP0s$G;TlctY`(Kd(lAuA83rJWo?G-jqM3oPEqBA0;lXmC;h`uW)Emx=o#*Gr)Fk2?4Mg z6Pv$Em4?wXI^;1nmKpw+G5PO$dwQkmQuSBbw*C^yf0jC_|EXI4kSVd)pMMn#F8t5* z`3V|w4~+h^@qJG<45*OelYTohyEM;*D}Od5;XnimPbxOlMEd9ZqwfwO5XPC$nKu-a ze-RBin*vnwImM~QYzkn*2s6xJl2yk-IkcISSaZi%DJ4_g0+DaZ$B(J8;x$yLAj=-SHG10>KEOA-l@d@Fj#6XX3mlhc4o2;4mNI%|JZb_ijD$~5ZbqR zqTcGWat)xh%~}UcXG8m1ZE1L_>W3;65wwD77<3(dx2cxxr$#TCwe{i{|C0n8-;grR zcu4m|=Zr_6%gOZgt$=_(h~{8bu+sE|XVE@Yo>U|il%c-3?%NL}@dl!U&fo-~UL-Sh2-bb+?VoQ!yPZyIoVjJ8mhHtUF5pECK-2P zY0R3=WAbJ&WqiC7jVzZMar2CPz=y1z5BtN`USauJJIpuBUK0xi@&Jrr?71-HF(tCu zw;VPR+cUTk7?^&XW<%6ibyT13jQjYR@ZqA9PSx5gY}6QQ{N3WcvwC*r#{{e$-IvRr zlTPwkZq|Mso5&Vev6P>5S#fQ4+Bu95+8fp$rN45@bWV(eh&Q8IsFKt~8HIHDy_%#V ze<2Hz^(Z&SphG;H!vhD%-Q6@+c!r>(zap7uoaKFpFSSr_n?dOp;;6b|G^-KP~%Si8yQ@p7;xW^eXO!dKDBgVOnA;#$UBS-1ApYUWL%5_RO>+q8f zx16bCq}~0|#0TUgn0FL`bu;F(JW9LsTge;$D>BL|34H|1YA|_6A^`1()6hUC0We`m!x;xjrbZY@#Y=`i$V$+fte|cB#5&}ce#UU~73>`*m{;U=Kk_;3W;~9w>1I|1oZnaGGO`7Vk+ioV(aE&8dV{C9O zmV15?rW!PQ8+%ojSa&s%khFBgY<5>3tL+MoimT95t97_JVVWX=90l%gGEY?Vv?w;J z8O84C;*hFTbqF`LHx`zt-Ez&Wj`T=~kB}TEnOVGUF%Pv_jdA3@NpG8Gn9!+QJj);v3m; z?>J}t7FrdV*}}mM^;@Vuh8v;RUcR-K8%sBTzVlldaa$Zv8{AYfGgg#4GZ*61T2|G` zCwlW)#S7PwY0Hl1lnpW-;)QaNw5laxpQ zV|O>G1oH|=V>1jSH8|ay;!|0BtGAk>8BPI=W3C%D=3>UNFhc?K;~4|d{yk(zW<4ZE zOVVQL`;DV!y2I7}x=Hsq`ss-SD*iphM{=@F1~>0FR5-@Ir%l9#%-3-)!+23pcn(fa zBxzNq;VZVLx(l|(v2dB{rgfd9H#uUqEX<;>PF20v!v16N9%eleuU~J1qY>jD_lYs_ zi57Y3RAHfIA6ZTaLx*`uiWul@^^=t^&|*&tR@O!E(GhbBiS}kG)6Wax#{}H@cMhgM zsJl{nRf|;xnQGh4lgO?#+eR?4Q1H3AlU8biLBFSiE4(RT+PEjf8RS9$^66!lSv1q- zfN@5YX3{=8_9V4%-^(hH>1aE-lAP1)AoSW)f(|dirJ*b2ld7JAYU<0&SOV0<6|v-M zv#Rj@EeO~${gxHfD86ZIJ^D4j<_ZmO+_QMZ^uCT1m-^R})FH!xw5n?9An{fDOh1TU zya=C~5^tcBNTcpoKzpLQyig=$6uGAfSnd+S#+Mw9cE9Wbna#FsaLS3<>^or;Om@^# z^qf*Wc&zp7wmR%3z~MEP?g*4g>Tt3eFdgLwV}Ip@k|NGAT@|D4cwW2}rUOr~fZh(= zP^HWba4^CP#0OESh6d}FDRRMgcK_I>Qq4^})Th$-hhLfDry_uY?2~|GXzd$iILK7x z|AL!gslc{`sm&bS?BKY{6$a=NlwEL3{JxnpqOM2u=~OJWeZXPY?c*W6Vx1{)F90KI zNz4nIpt6Mt^P(u4X*O)z-gd!vLpek@D%!rlBBL0iIM{JPs(T|L(AB5#WYOnRXn3Gt zdFLu~iq7l`+spMM^dH1O{cdkg=gRDl^sej9cm=qu56E&TH$g*Y+=uX%zH!tNe!M$e zAj2hc2ahF4u_=H5PB~&s{l)c83HU=srLTPPL;Yz7xs9$LsuY87YUils%%j4(=kJB08_wYtX379w zU2)Q8O&1GFDRoWW8=u3v)w%~=lE%EUy@g$|RU&~+%|vwG!TUn^ui#}GUSB-%FL-%} z_`iY|jeqz~A`bTERu*o~My^&4_WuMg$#x2;LP%qOwoX?=_=5wBib$@Ba|-rZpb^!W z)Zox1eMRaV(@2lww)NQVRjf#u?!yQN5Y2LWbqZ>>hB;W8SswGhu5~{?=H?85PVN8^ zG8q$w?9q5Ja5qi@V>7%Qubo~4Gr~C0R=lS3FLnZVSLz%MdJ#qqPL}@6@MADBwKLD< zaACW@qt12UN-N4uxb2Fi*vjc%ds#w2!wYv+9|v*_G;Q7Eu@()kjx15)i*}b;wi-jo z!#!KuW)d{rUMuq)*5jVre3qMfUd^jfcdu_UbM2Oz-?hk4e+FH%EaTLzv2W&e?ls2D z<$3wqdX38e($G6C-nsFnupr*{-GW)A@99yjop6}@a8_ybZj5M7D^*%pqAow8udBSO z&Wfn|^HL=)(Vb)=x`ABTZgD{Bzo#6hN+>TNF?-7=nrhim5h=2C?d`J)n|MM9I<#HE>M@V4cMf6O%;o zQjaBwl1hQHR6@$k<1XZqYVb)(LTOUXi;yK`g4WUrEpW;j!DrTg|4s5)Ykq>0Ag0{Q z+h4H%D%(na_*Tb%K{@tc#KZWX5zoK-yOKuse|~@NVGYcVd;9@B zdvFxaL~ojV-}Iik&AsQk%w6sM`FzI={Cd+GqK~QY6cIrcXU!R|h~i*-BY#YRKsR|{ zr1wCjrcldKzfTKSj{$QMuY;DFm3Ed7iK`@7BvL}B2s47C4tT=(N&K27Pr+b{4<1fMh=Ri3sn!$a()#pH26izHyN0pNZJ z!(JY$L!;Kf!tB1$VLmL&!)|OY+SBby+hI<@ZvV>?leISV5{k5%NVSy5`WVJuN|Y@u zsFh(#f-(X#iR3h^O-$<%y%FGYUxGa(Jz{CDO%=6Vb3m~)sO5gMa}}AQx&M_XIcmsR zDXgw(-w7qNKqYZX>hx+NY#hHQ;I?~ER3 zSBq2+M8z_JP4Cc(W9HmN7A5mo6-rnrj`Hf0<#YxwCzyKg{?_i)19>2kW0*QBm$(D zlrBEFZZhx;&3cAG_osC#(DF+^NH2;E0%r5}IUYTxX3l0^0;mK< zz2R0=#RHoRd;qh_X(p^o*DNfvRp+^Jr?<1=rsmN+@BXY42Jaus^eEK5=$Oebm6t|ahyzT+6 zbpmWV&9K;3-oqqh^+`D&cn;~Tr1#se{ND_xO29cBf!Q08FbEus2FW74b9?mT{S*La z{=}ODs!_Fri+KLfhi=MU8JxR}t;Tp&1}dUp`?^acF~nBO8s0!ep@(lx;iV@L)_Ae# zyDyM{xi9j!38)wbq1>|5eNhJkZ}8Nxj0 z2xT3pxJaWE7ZH)$^wO`$aReZXbI(ZLR_J0mIgh_|NvhV)?@)TEE0v^&_y^04|NY;SCx9C1L{*@H5a{eyG`^H<6S%kx8VOk{;SC>^L{CmnhPVQ5$?c55pD{NObBgG@ll(S zT__9x0=}D}^Ko%;ocOjWC_x-g#%7(K%hBBF@8v=t?gf4T5TZpcZKOsIl*ds++ej?{V6wPHR{+W?nl$ zo@|xEB-~gNPlP39<2+RP2vx&v_=!8^CyibCCc?8h4xe4P>0BN+jsWxUy7IRzf~YJG zHeOkxu(mKutWO8Sfe;R&l4NnDgfK70A@nhHF7wdnpHGM17P`eC?XxsLtm~p08Qxy` z<#hQ=V11;O!23~$)OQzQbhW`WB9K6!L3S}PjCx|`U@(5LsO*t6FsEXK(R~KkxD->8?RGHBXi9?^!!MJ zA(}30|mD~xL@?Xcx zL);hMD%~Z?Ym?Akmhq(PNneCpwB`<5WCN67FUo{*qxWv#9lrbl{#TKlb-s*3hXew$ zM*sq%_|GD#Xyj@s8{zJ~FL4uqSWjqM`VX9st5vA~Bfb2$_X(P%=w9~Ls0=^Cz zC4|O_GM>_Q%C!!2jd&x*n2;}7T>NB!#l12dVf5jVlP^eq%z=uNFHU~qh=o`e{>Z86 zw=dqkYfT6B#d_ijY<~Q=t@|g4#Y!_cG z9h%!c!@dRER)SjtwsSgM(G6bXmGG)ZYOk3M4NX^W?)-MCzj&*xTy`8niF+4@!v}0v zHw)oorFUE2y@j~X4j{!=&UMbCzjh7PL8{}Ity4ETxZBLKTLn>D0oU&giXSn@R;!rV zwo}GfZT(S`gu391=q6%6Juhlkd@!9>D}7r`F&S)TQOHl`(+TR1N^cH&r@D?T!TrVo zXK~d9LmJLcBT050HX94q0V)DL4JR->xOE5sMXaMjJM{<+%;!`h0pu~4pM#sfo7_|g z_1)Z-?icZxd#?b~;YlX5!IK|cmv9N$UD>&r)7L0XB`%}%_KBV<*`peo?%+;1_=aIU zR~|QcvD6WY%=WnED(=3|x!fa-;T+5PRN=MdHQpCC~!~^VMpO)X)Qd8lbm$ zN~E3B^BAHzmsMkeJ=+vH0@uSHHU)>cWfTzQcny;yt{s8OFJmmO22OKz7K z)un8gDCF`t>KTaxwukmqx5vVx`enp#qPtHvAu12yd!(Gfa^o?Zht1d0Ij#T%6>kw} zXCU8F_Ao57B!s*c3n)?E(xBF*36#zPNG5U_+I0Xuy?&0}ki4ZT~{TPn>V zN!b9>HM;CjmAfGBM1B0qW5+N4`}sds=Ke<$UhyX+CcM2q;vU!GOy|u0B5(6IsGnx)M{9Ey<4-28(D^pRXQ)5UNH81mZ1H|-xqIgOj?jU zk6mL_bha-CLzTLI{SVe)SBnO;R$}F&yXL|5S2asnM;BB6D3rF*XpU>{z|7G{pS#?X z4&CA{hhLs>HPjmLuU6Af)6z*r<$_melrl63gi?s)j0YpGjHxnY%Y9~DV`QE({aJ|R}*mAYe7WC?OY zR14{`2-@rBrKJ2ov3tFn2PCiZuP*6`k3q!Eghd|np_64Rq&WHdxq|As{6MW)n1IYX zKB9F$jjMTf!4pJfVom1GrFF-gqI;WV?t|K7`azXvX>4A`Btol~VsRgXDYu95o8Na` zWRJ)I9C*=Y8KbDx6a_Ke=|cEJFO=mnbM%E-d8LP}$1=}2R@~AnrIXQqh#`B^xIFg#jNlsiB&Ta#D1z^j55MqqN>YQ5z}(bO)kwUAxy*bt zndsYEZL_VX&4^%bNdhaPz)M%j%Wt?}HEfSF=uf(rJTr5O6q3*!{_tXbp%Gv5*|YkL z@T=$^pDB&!ZC48UzV9LHc`kBY{>HC&Qbg+newi|UiTX9o5U(7fxQj6SO=0d(Uq#>@ zo&fyYN6oQ_)K*`#$v^*=7v|h;+rj;tC%>Ws0wVg)7ps)Li>r~X?LVSDxmvKkXveJr zl-(N}v_mvVgOfI*Bwi65I7skP3F}A+cZ@_ArXEQ#SEM(yNussd(b6k@iaHDGHSxxD zYD!Y`fOTuXwwJ=z*47nu8;8s5-rm=j-K`Jm*8p>Oj%-t;Lx%n@^An$((?2=4z6SbV zA4?KHEQte<3ixN!M=4`TVhyr_L0EyUMkmT~3YZD%@4yi6v**A81E^-UNvz4By5lM( znK=6-c^Cye9hzC^Fp!|EsTSj(nJ{w?k5@o*Msf#BpsqM`@ORj}3f|HsTq+0ez*$2_ zpt_T0z*R@i?==Z!%2`!Tx-)Dr40n&hVVDy!Bfwd6G9>|(`RNlbosm9iF}e5!#&yq+ zFkW@E`!1epfpf=?AfDAKo^F9@A(*2VrB(@LN`M+(a8FnVwKtNmEz`v|pxV=GVC#cu^j+iv^@FX! z^tX5A_YT=C>ab^^R;TX4LLj?ScY%m6+qX`UU)Qwz^z35QQ(rwQdC15VRgScR_zh%P zZ=5$LG$m4i9JqOT`;^h7A5>u;RNJTp_L;b+`dagpQTo{X)o<4CJ=(kcbo`y#2R0eO z@Ub=*>>LhVErpeCOQU5g*&J-O4xO$dJ7ul1VKeEM-A`GO1eY~dttjR-F5pXVzddQK z&Y5hY38aJ`Y%+ZlJuS);4YL;T6kJzbDV`jME6%0Pc6P*z$~Fjwr2{y3QKN^S8JBF^ zf5^d~I~^?6>gc&mlpx#1LmhY8!?ORH{aLgWv#Us!%Ibk_Gaadf34=ZHi<_@(t7)Y} z$&&W~B;m1^)ugO7>O5&Ne&OhObQ9n z=kOe%uzC@X$8md#Rw@k8+en1sK}H#Q>nE?`NI@hqFe^q>E$j%{g3TsdmhNNRGH}}% zd#yCpHrbZjE;sq(<&f$D7tBya;0tYSUJq_SwKGD`UBM$Cey;V9e~(Pdc*@bSo+#N{@qDN_v6Gmi$N zP!1gLb*V%t8axFpEuzhuwP94Hou(`3T_|OoGuL)fzEdnW5fb_dcelwH&Xk72g_H$U z(_yUe$LEcGokZ}U-Xbc9v>&P*G5I{?`((kb_kgn)5B`gzg$e?ZluAuxg_W zll8KK*76oxT(lTU9ak+aBzBVUlBLk-Qbr}Iva4&*hr=nti(q4D(D}Tk9k#n6VSoU7 z_hRUwi>?XP8uGjNwDgmipV1b!j7>r^j+tl@8eZZIFbXF&$)(Hhu-2JHTy|3v#n3t` zt!B;$XA@d6o=bAKD#EHEU3@Hsf+#KKyj}FH zPJSS#Ya|=d( z&Z?A)O!z8Fp&A>8_EtCsL+S`--r!;5$x6@eh=^_)bUM0;yN7*?sU#g?b6Zo#iu@_U z;mT8wb!OS(<5RG7f1!sOx9k`7SB`(-A`xHlqT3U8YF(j?ns+FH+PQciLClz{<7ClX zRZF(L;<@+ln!#?hz90wHcZ%KOyVGAs=BW+`I%?m%dr{Z#!_qULHBx7OLdOgb=>=kS zNl<62t!`=+DrnzLlRoe4VD2}eIga4S-a-dkYJDO7MGqS9@~N-)dgJsrW+8(f)t_wN zU6ZeO{;9Xe4w5eUldsVzh!vkiUvRiT=MQ5mGt9(eZ3oS}u6%VU>DtxjPtwUwZ4NpT zmyMldM1-u*&1IKN{4&x8{BhIq)N9$wI1FZ@Z15$2Wi3SeaW9tBP0wCdi)S(o2l#y) zpQ*oR`wGInBuwrde#!F414OetP-qXepOU2t9)>>cQg4Ve&WHjejwKAyZ<=W6SWL_H z=ynS`C*})>gbtQujL93>2bSIBRd1KNp7g?3?Xj3<7K?Y9ENuA7R@C%Rnq{6uRhzq9 zVPgwtJm>~aZFYWeVrcu}(C$$7;5Dd~{#4H;h}g_puFc8}bwVj3#Y0Ua&&mt5JP(D4 zS-)DGYK;@+tvb(2l_Ve0mxouQi?Zq*DGP6^Qm2th8)nW_N{&(t&$+1?5jlUTTXjbp zw{&xlWw#bQmH>~9uk?*1)OdqV%|{y}Jn_F;70GO-Pn`cC^Q+<&6i|7G5-5FGdSHjj zU&s#rCD@HE16eq5ifubjS>+V|lU~LDG@`4>X_+|hSSG#dllB&wT0)I~bdKs%FSVc2 zkd^@7#wtp?+6dSv(^>wKpz5?G&a+58`OHWE08{mwUm)ejrcxN5%Dh>%`>3jaq5(>! ze%eW@5ym8jH+BD{kD^MX09l&;lq{}(L**xECi};c4SU(cZ%=BJHW5BA6!1nJhe#}M zWyi9KPEBJJd5Pgne0B(*rwsCij6uAg2HeK%9K^_gds8>K!iIV~+`4yik z{-7p&^5hi{*>L&&BLWiG7uw$yPsD$O58BnfOAC%PKIKOjiziuA1KlqX_iS*n26I3M@##{82yNyMLzcpYtGT&-2s~e9t~lOpusqx4eQpjvm6LnO35e1F1K>GL;>ianTWyT<1fP>q9OE^Yr*#q3v?g1Px}Iy^i1IJQ z3Lii~R6xA2|TgP5IG+*@V92>yoEn>{h3?1alaOzKOByMuzIXs@TY2^O!sX|R`i z4?%z|>vPlwbFj+PO_C+Z%e?X#a#Hubp7)bdvP!1e_2q1I)z)*zgJNiG#$&WdS&h%j z?=`OEZLG6j`cmg59Sc1`=TBiyj$N>al@K+E$W6O;nFd^JNpz2?<&$ts{3>I%(uYR% z-fvPG9q-z*&<#S%!4o1Ml9ykZHQM~~-SuM1o74pNqx>M-l#m+qZ6Sn?=b zR^I76oU7}YhD1X~yxz)Z{hqV$YFUFwg9XI$3DC!_-CkZeqI;Ou^GR zmDEGm&@94O9uED~wE93JW@%^cwP+=!u<%JP@#!}?UiS56L8^)HNrepgMEV8~?gRnu zVkz}fX1Qq+I(7~hFj=JzeI&`CKBdIlDP}#zN=$ zgO~?*d*e@Hj<~Lx%8AyW4bc^-2WC~cbd`amPE6MRh|JwWxvna zFbEa-a%cC+`UsH=%AB#UuZ6T8yYlQn&zK9&`MF}6&y;4ma_ss(vDLg7AFnk+oT^C> z+6x;1k#eBP3kd&o3vt!f83CHHyr+GX&l8<{vw4i}@%pebS7YqYH>ZEZ@Ve#tPMMruL?h z{1+n%2}CtP0VMH==%(0S2`HltG5I-h&0Vl~XrCD3P)+r~^Ooo1L1z@gqQ`!jE~tQT zd>QZ~oH&>@-Eo7Bzs!n?E5#7U5~P*Cj#1^S7PZZzY8wG@LH8k+I8CDTOL;`KID$`J z(FLzG=y)<{0nI!Gkqb(J958=(MV_}y;BL}N%LoL-mP7nc5--ipG=zntf>*E!Gt_dQ zJW+)@`G`t^+NI`(Ku5b8@5GBK8pw*WRUPsQ14m3c2qFx7I^B}>B8`?duZ6~rR=WPG z))~yFDC*Yt_$8E|OUk#%+U#h}E_UU*@ZoFooSeqgButT-ys$<25m>fB4-Rc60}=eG z5Jdj`=6SIdJ(KFqOx5P3d}gP3UZ|g^8x9IvPD$0vM0mddiQs}~SfTn)ZyV6Ph= zmP`b#bZQdmUVKvz(Ma&GiRx-8{S~X2PtQwHekJMg(tz93saDH)g+o!yLhjVxXVh5KkM7W)ZMB7T&m;q^lwVvLV9S|1qgYd@_(a=_w_elkJQ|!!ZDBL|y*SwTt*6s~uJzw4P9J%Yt zY4M}7x3h?GS>d1u60qkp@4|@d9mXDCJTS+U1<@i0X)PLJg%HN-kV-MH7h%mCSYr+co`9{iS3dFH%dtQ0_}Yp>tAIq<~q9La%k}z2d#WHlBu=Z-i>{k=vO~CkDTSUCx)_ssVeH zi(*8f6;SM#z#&3nABy%iqfdqX{a>p(^OQ(bnO9RV{m%iTinMMy=L_=lS zKc=TkHId1mPjdw~k?WCM1iYyaFt(Q8h04Pgs5wR~%Q;j}3|8SVUpAW*Frq0ltljN_ zZwBXkOT@|{<IOLGlbXs%I$qH z{9X=NaIZ5B;dod<^vKNQahaX&HTimWTTA zU@(#jhh)N@(mWXY^5)%7ig?ycMM`HRD@L|KSv9jYR2hVPmUQHZe`^?t7<+zG9F=&} z9He|!e0SCn$4o*|2JuzND%@BC;Vrxi2XY#fWde?6nlYs5oMvxcUAD_5`_9NzeTH9I zeCs1ZyVj$lA;M#+b!D}yq{Lxy&fivp-`&dCRq*_mvPB@T{t2WiXJiM&)bYqBYtDS9WTzbEBeIZ6wb_RPw&z#HDTNvG|%9Q@b zQr=B<>VgtdN6kAIy95aXY}u+M;mCYex{2#l^>6%+WIH67sE*1LvK`D-H-Q^ix>Ecn z!Xk=0y5)NEooG;83`hu~PzK5ix-X235QZzI_Zg1Zc9qx@$k8~T#ats`{2*}taT&EX z>Wa!UN(5N$^zdWLM->`c0)~I+RLnGtbv|sZ)h_N37Tn;F27K<0?cRAP9%Cq8Je&a4 zOJdoAyi@3d0Wq^R@ps)|qEDYF8D2uJ;Zm#S~4eVnd9Y z*64UsEy>!5c6(VSzQE)rt;%;p6alpYXMNHJqG~j@>aAlVBpRunV-!blQdixzwrr_| z2UTWsNAY1_D{T$U@qY7kUgiBKk4Qb#TESA+-8ZE0%1n8bVUTts`F5R?dG&?tn_BGA zq!_pLW|os7e<6==HEWE|-qawP=z(=&U|$rwa!5%sR9Bwv9Ig>ScSVMbq_^k+LO1Wb znPNwks+a|Pr7S{_V9UDn1sQppiH^C7NRu?44JuEp?%Wr2?d;Dg;`gKAK3(kSWlQuT z?fex!clcc}hTB>3!YLHbyh8CIjv-L}l59LGanoVM9}1oyCo_eKCd;)sgULt%5gB(e zCkHb_m;ym55?@R>6vL?Fz)bOLAVmffM~k5x`_Bdxm!qNO;Bxo8S(LuO`GP0`gDkQi zX)~Az+6d)i(5MBDxh=PYjCICvJ5Mw7B7{J_2-9Ae(!dl3VNAaS_sBkwgRh1}Viu!5 z@I*p8qFG-LApBB2Cp1v)59OM0XcFAr91`tw$VjAiDHxs<6vua%#GV#ruqYNi)aq|waI*gZ%m#( zoz#QeVPIiUOLhRrJwlgGlYHgeaz-Iw9>L?DPMH^fvnSkZvcLAoYa{pr>yG%ef8gzE zNvGwA-UaWuA8x-R{%ZnXa2C=hDHaeA;6LXX{#jxw=4oc)_Fr$zQZ>8`@zk*Y z$TdyavoD3(C$&*g(URLO&WKIzq>)Og;Eb=>E@l<2PTa6+tzU(1Y!E=f007iQcqC|| zVzuo;=Ma&BMHnNvw;%lXgP;y~uQRgj&0BWx0aw|ty|2BuZ+>GwAHQ4>1z-$&Q67%y z{I`(@bV2|>bN#o`MX?be3is``I>+MM!5!-f9S{%kJuQ&XI~XFR@t%(KgjA0V!MXP6 zhI~vp$%cH6pFT`I`x|_T0ud))MVcOrGX2N`vEO$Yh9p4WGJFXWu7{YXAQ)-(Ak91h zff2_%ltW`*o@9X%BT-|aU#LPkaSBnn@l#hS%pa~m`N)KEZ}*{hc!{|mrY~9o{FuWV zoLB?N4`04O22lIaz`j(043KxJKz-Cx3h(!=L|viRmk(dza0+UN*>7o*?`?#8&_$Sg z=;V8_haYc2881Ub{-K1B_o$z&f%#MwdyBaE*f-ZW_~-a|>wMhX?LL;CjujT3rm{j6 zx6F3+tBK3XsQ5}#vLzJkRGN!+C5vfkP41QxF?EJ!d4YAamhlq8-zSQvSLv%EGQt}O;XAM|=fx{FCehWNrz_;|n%gO|#fYO~da6=*b1GV&TxCgKXWxo7IN z_cya77r&_^Sd3hu=n!s}rqTTHr!|+bX(%Vf3tham6-HW}vKx8LOJ2w*&}uGOrhmji zt3*>i$N80sQ#6~DKVG+a{Y|8i$DkpuTrtVwxMmVGw~@)lg?kD99GQ7nN7L})<>UK! z)(ju47+kX(KG$?JASp#OEgN-n5sj1Kjm=2gF3f~3+z|_!X$>bXbgLUE1j(7?pj3vw z^aVdfZ*4_7H}Px`2@*DP%e&6|V)EM*8?%t3!0H_x;p(#8TrOu**-MgS;TdBgF_|qSUk`GMT{M>#swfz)61GyNvEFw|3AiVzDJpAMkod%a{HQ1Rn9Q zLDU5Y%2}nAW^lC{k;s0fMq3Tdh>&L4{8iP~wSWd-XHB^o1NY^utm&OMc76wf|T z2>Ac3P&iA&L=66!+C!^4zxXMvyjs7NfZ8pS&A``1j+VSkLr0QH+qGtfg>k)9_Q7^9 z$pTL9G+&;HDq2z&iGY*nC`xU~nI{b1dL;IXuvk1gYcR%fy$xICsWa)WGtsbTjh)bL zyUX~c%08cqvEWCFOH__dO-VDATe?ktg(B4%!wi*OnsVd2 z^`?>)Z*2ZU+OIfZeoc0N_*y@^lbBk6MGqmG4 zc2c2f1Cq~ z3wdz9>AU}oZ#jbfQDOfk$7K`qW=*_eXP)SYO?zs(>mwP+8cl(>?H+h`Ku>%7O^Ezy zz*~OkHH$W2*dBG-dQ*b+`TO11Nv9<$rh%Se`m|1#1Ur54#bWvwBaN0CT4`wJjuKFY zN{}=z-vj;a{7lRB0`sl4hq4L!l~kmm0Z*Y)sxmJNqPV|<#@(CKQq(PIbSyc3+$nu* zLtYWJGh3%PM{9UCOe~$Q3!NQ|O{M4eY;ddG^+BQ(Uv0!IdD6sP2Lbytl?elS89eC< z0fF=doDXNRyIivUq)n|Kyvmc+$f?F8Sg$jBJIwb~@AE~cF_!#DJvDIYU_F>xsWQwR zI^$-4y}LsJn9>&xYBz(|z8O%p{*i&m-dD6FDvZF&c=7}(qScs!A;{i6Yz4cQg;Pw^Ayas zyr^?8^W!gAE$xJd7a3`87Lirmr(DZZwM2LjG#MO}w$w3yBc>Q8W}TPft-6>IezJHN zl}4GC_2?M)QaYZ%Sh2l)@S7vF?~htABvHOLlMK}qRp`}Zg8O+I$$0NGh(#XWr->2| z?=uyt{&A6dF-d#(SrO;XErZ?Lm-IFMezl6gaHqV;L>xgb1z?)ff|!{?Q(6@2+%N|O zGm~b3LuOdOXd3RR<}8aKi)-9ej>@{pWkRNViYhvb$B*})fWrbXLcUWooMQRI(7)6BV`W#hQ2 zzF|YjWkbnhV`S_ujZvLDqLMozp6wLd+_tJ^)3la_SZGu{7fyOOut4It{9(TEu>R$0 z0)I2er+Es}__qe#J}~}rg%iJ(Gek)MmGXeLE++(Pmb?|YcU_c|eQ4OL1Nc-$oU&9m z(8r0m>8uTyH)MW0`nUrwU4=kM7)6CWrJ21ViZ2^Yf;QQUo4GfnAGH$ zL)M47{HwbUJkq*I;j@-4XK<+tXRcPaKZeEh;WW0ko4OGKywb6I*;!<*vYTiJb#D|i zm)IQh#_=zB={>wzbC6tA=v*0iEn7IdLnLTB_sU1xi%;GQko2wu5sX~41u^8Eui8R7 zGx-{BaYG<+D$ytGO@>Wl-x+Xo3>8>n+zU%GprXT}ovw_Om??L(0`%s?! zuB!P=o9##Zn|Ed|1J5_=xr3(d0~@E@XsIM|nRMt@?oCMc-<#SOnJ*!)n5KJd!w=eN zcW#{^Db5)T-AMXkPv}1Ge5A_8bNV4`54H*BdyOK+XyfVc?E74`YqUfB zg(CVl97VGs7rdnCqvo)?a4wZ1^D@y;uf@IXQFbs(aGN%*d0_2COX%W++oU? zIuvTv*U;Fk9+!Sc=XP$hFL;0&S20&y3yTE3c3F#R%(kT0^LGR!s>^5)b*ABO_D9^Y zkxgE0_6!6X8crtJy$g=xZU~lYDgf`3JE)FIqZ zN6`L2$gVF~sBl0P4kUuWEXGdzMb-5pY9JHFBIcU-TX$xnpWU9RZeA(uCmWQkhAoKK zC2;V{?xSXMkgtWyT%wZ8x_aD9opo`)nz}l3ZP5z;>_(Wn>U96;a=(F-<9oO*09uZS zqH5lwL&LdcYU|XdtC7EzL<2+C_EV$eI2ft;aEsdPQXRUmYaw`kx$^+Cl~*9E8^0BG zcdH3!-Kt+}(_C~BhIC(X%YiPhGu`nkh`%fliFJTGpE0nc=m07q zM0HVIGSn}gL8gLNakaT{n? zNkTGXGd&4agun(1mOI69E1K~;kMOz!py4!BH+xcF3WM{hsM3sv2PDOXtMjewlFl*G z1$}rj4yo)?L|5Uo9zjCwSddE=D=yI(xn~&0*N!dO$#bMEl+ju?n2#s(0>nSbxuJm3 zlN_Xi%K$e@?J#%cWY{6DLZ&(LzMY3fKz9O9Z?m@l1A@y_ZiMzjSyX@j#ZX%7HA?~u zL#2Hljalz|Je%lIV`OH9TfczaHHeA?rUY|RC}x$!KIU6$?|!6B*4<{4cMZXC|Ta2dsJ_6;ChB`LLIepcipHgW=(NE zW2j5_o?ik1KlbII|5WbLzfdPw91C8}ClqYGwE}wfZm_?|A{OHN@Ngw}R&eOo%D41z zpToYO$sVmWO3O#;kr>klwOc$`F==lMmVS;7iUSY!8ISwS4O?t7b#g7DS_u+{k!Y+$ zcYh{=>G-Q4?o}$yB_eRJa&)CyqR<3s^vaD(Af}utGEB$wjXLC!_+(H+1!X8AOK+7} z6@oU@MXU8&QCNY8*1ij(4aLhEwx!BNsR@UXNs6QqkF(Z^gQ6r+uWsr%6j^V)mR)ghP6mA5>fcsv0XMe;hWr%}1R~qJ=AGV?p zYpsrwvdbn?neu#q&b8M$B&=u~dqsrKEcY~G8~T9#D9s*~-v0K=vMso<^z1Nmrw5PD zyWs2;UB7t1M329eP!$%pn2OXwSEvc7$%Kj)6;p)Ltz>mKX5YbFyNA9kGwfb=iw4s$ za+x!v#%8R%tXAjUs=J2(8_F^Stxgv!7~St5Z!O|8r4K1hT%xMb&85Rg8LsZWr4TT7 z$AEC;?og_7@sveuKC2pxL6~q~=*T#dqiMLBI`ep~yTup5ID)4P(qShztWjm$g6EMl zRq@gCGgwufB?{@RA65!lh~k;)Y!9YA*?;KZo&bZxr*Z7Kp(B%*h8IDboP?1Byt*5k zHfHZyJ2B-^G^Efj);^s(7%d_XyGf@MND_|)PB}k77pyR-asN?8)R%Ue z%oY10`7Kabj|g)CYlNC7zm<@)$vOK( zQS(k(fNv_~_SJnxwYxu%fCMQlt=^brGOM5gByQv3-hw-DAe(*blV@u<)#{h>hhQf& zp2O8U!z*FIz~<-tEw-KOw8xf9+A&<2{czs3-UpDXK1lPoTcXgf9JX+GdIuQAz7K_E zje{?P<Kov(I^&O_B z^-UBvKJm5!w^z(PC#Pf#`W}(+E2+>uAwhD1x;W?a0r+5O6Tt{0fTPQYx63A8iilHN z$_yCVxXGRZFF0qO?QSlaxP^J~0#ufXxWtMRcx7}se$UbBJ}u4-$XWbYp?6P%)PjC$%@CiaH#vFf>3S2pq< zu7>-H)hC$I{bSe&Rg9W(RgNg$QmPX?ZmN3$ zENsR0=GZAkb>=hP6ldxE$9cn0+V;^*n!sA~s~!mDqzraNH%L}Enya(iVOJ<_%baRy z%TU_R%gHLTJKEP4^#M$Ny8YmgS3;z3XokyQ;6udu72&{>+@zi3%8(>R^D8=q%I83t}d*K2|7{!(=0BoH5tRJB#g^fM+#~S zYv}GF?E&46o|g?>ou|Afqu!*=vwibp)=%`cSz|j>H(O6NhBM%ADDPDH$D}mhRHO@& zq=&GJ9Z9@#ic@Oz7F!ssU77Wt88nKb;1XxWrEE*C>Lr`@!Q6=UsG+=(64VhJQt{pD z@Cv?P+g)v&me75yB2q{i?rfDh#V2KanB&IspUH>mQ)IX!= z|3zKPQnPf$Swh=d!PW@pk-+`-O(6u7fslGt5*F&atRM=vp8z9~?EbKf*6=)G40E=E z7zueELT+b2$t-YDsw)AanG}v@B}XA#j7wGZlFy}>514PRF+r5kMEyS5FHOGZZRV5g zh8xb`e2+7qUJm%ZqVL{V?e^+}uEJhwgU`Y|g#Dp;5Qa!bhM~k5{#Zh=hD>hq9& zP-8;h<}iH%Mhx7v_apbiD6%8>DbKPI?;0n4$wV7xL~pf0$w}OF^SsAvxvkkPR|{14 zWGC3rRE@|YDM_r`%(0#+8^dI#sop@6OYAhP4>b%)cO0@nvU%CZ7(X8Uy#q5FB`52v zA99oE-2tnVPR82wIn$n^(`*Z3yan<_~t}ibVLJ4RGW+ z_{IJSCqb&dn^Z0~@~39##<=M9_ z+=gx@L(XG)bBD>tCmch?5I50rhuK>iEVZQcO2u!`Z@C8y0oJGyWUMP+_sOTDv|B_? zX_P(dE9$x#ed`(2KICP?Hw~PrwDB>Se!FD_s- z_V5}EBVW7JH)|Q`Kd1g_op9VO;qn1sI9v6p;^EXPh}DE@*;Pc#tX3YdZB`c`(9fxP zQ{d4Xw)7`O1+A&nUuK@2y>RNz-NAH@d;Dq@bCRBDMW{J*!?QhgQySFD_r$rO2=asm zoLsyVmHGz%WY(-QaB7$3`5+5$b?yvN;@Td8Kzdglxw-YkBiKja`V+c<dj zaFL4i!}#(+Ji_Mhy<9s* zdnGT@BPTC{BRhjfel+cdl=ulUQLM`94Ms>%4nE_!=BU-(cDMmk9>seZAcxug$;A*N z3$)#4w!!iBBPk~`zBNR(!27}+_)KI|qU+NHCi@$EKIgJ*oUG_&<(<2Or8nSI!50Zr zvQ@(eB~w)Ji;`;o6L6arXds!?VU8#2b4^m+Z7a7UX_zD#tPwn>?6-we+V@cFMqj3z z#S~Q9P4(W5WsdIZfe1{tTZI`oH z2uqy$8(?m|KcP{_$L*F0aB6b_kFT@Uh|SJ#TM*~jxXvv{?*pPW z1T#V#-)FCTAkBVFwxz;p!qjR2KYr}kCVQy0=r~{>!NNoj5nmC}Q7*}@(#GqLS8CRwcr~fh@EU8O}CLP}$Fm7mlcVb}v zgH60nX_j||w~(qn|B2@KC!~EsotK!;)ZTcB!3XV!Qc+S|t_qeK#7+-U`*p)9WFEI& zvq>Sq1bqeeZb37+N>h*GT2eS)biiqottF=l`=;h~tw#4y%Zv4W zEhzzHP@=|QbOJ95>3Aa9_BFT!nTSuxKKa|cAH4)BJj{UkQbe!SG{@g`j;j+r7`{NA zLlvBpdR34Jax#yTxHI0Jj|yZj)~us3$~g=>r{Ouosv4a&$ge(|<^?MUx)LIXt83E|7^&!8N2wNiMYnr3M9e0R!}vW5TjfK}-rWx+els4suRtz;nGwx8ye%@qYv#!%H*TvJ zy_gn>{3DL+qeqb7qY3D9m6%^3^UVzBZ7{-7sFYHQJY}!7Pk-{v|^H*tte*qRg`%&t5mh#G2Ss2PcpVH zkyIz?U!$LYZy$K(oOqs0B>Cp}g7vz*D;XOG*Me})ZPH_F86QyCsT|r%59dRJji_Yy zf>7}VAw(RL7|aAx;rELfdr1$EsIVNMP^I5WpdlP68N4n)<12i*ZK^CeN_XyF0z(*g zq;ovj`Bx*TUK&Gcx2=&iR4?h_Q!gGs+cUx)0k)-Xz&px!w3*7aO48l5k(tQxO3>NL zw|HPXD!05~J8HKeU~*$GdEpB$agim)JR}Dr$bAOX;FUOV z#F9`IqQxV_PcnF6Bk)%RW?BtFddrd}aGGlzHfv}8ja4P}!@c=cC#Uv$n_9T>vxDxc z9FywDLEU=dkTC>HF=rqeTha)FI^2fEZM51=*2A@UE>8BA!;`I^-y?-<4h}ROS0vj_ zzQ2f7p>DB9nMxZH_b^zlLOiZjV#FpbHZZqmAw9&;-b|aOj>!DNy=g+9sw^tOe1?;l z$ebvAXs%=G+lIoSj5f}@w#kSnqp$h#R{uM#FQ z=CB#2S+l8JO3Jr04r+GbNpNRRaZU3O`kCwe!*S4U zWyOtjLYjm!;0XRF;G)X-BUPgcpNwP7OVu^%?1+N2GRkGCAWqV6{83>DfHfcuRb4|R zD=9^rR0O?2QzAYad!!5#a&^cPHB{A^#UfnpE|!cnOMhv$8etFDa=?oXV9eK7W^pyl z49jxe()N{=7Xqa}D8cptn37($!B^S*`E*rl^^zUNf}1%2!=ks~hzJ0XW-c&9L1EFG zbHuZgakzXG*=#KewQ6Ud)FNx5N06gB}n@nxf`A}(vMsq+5XL~?}Rs+JIU)F$KL1WvF z&)rO4GX0>H)Lw2D4r{O=V45vY>F(>uzo2N^1cFo_JW7)JmLxKaWJI(4Ia& z@5-55hANPh{^VpVP~{bc*86jEk`0Tflt5=&ri!1na@8tcnZ{xv&U<-X@ zI}C_Tj~$#7f4^fkM2oYh%Ay-W|IT}lgNBb$_s99KZ`W%P-c}SbT?J)+Yj{a zBuM@cPw^?&d*{~>AjBE;mF8n&_H)%o zb1@)si9@*|lk8|?8l6LIv%`to83@{r26fL%KzWXXgKv8wTzi33dZp0kkTBs9vEdP7 z$sq-zmp|Y!u2FOeu3w?>46JW~0szs9#Z=s4n%Kp@N5;=Q&&Np9-<0GCfx2aOATWm` z9);ZUjjMc9#X7DqPOVASd)lMr6N-+7$8&=sFsk&)zLJ?F9_vmA_1aBTrxDMsT}c!F zbZgB}?k3+Ha3S8DaG52zM$sYMq;J=A<9Bj+Nj5n$wvuC!uOQ04eI7*d7bnby6b-lw z!i_tCDq_zzgMX?cvfe+rjXoJKySc@kp1tdm767K@+4jJ9hCygc8o4|m5)Lm8_fLYy z-95)u>XYMjUf3S8(g!~7ilD5v1(5uxirl661a4#%E)`AP=0ne}OwlDk@8pmcq?L?_ znY{r@M|)cr=w15!4_!fy6+|Wfr6**+UVnk>|B1w>b^Rx+k@g>?db=+y4xQcqVYw?! zPsveENvMcR$V^O5(2Pya94Nw5%Bb8<&?_;ps4>*mGBGqWv8dX)z$q{=F|Zs_QH;+> z%Ft0Z%g{?ok4Y@aOphH>QB6^gP0BPK0soCApz+1~*0UR$o&NeD*k7{u>pT<4z}ei& zh}OZ_(AeD0>2IHcRZhTWfDp0sA&JTi!_bhR^-#5JoK&F(4G<+{)C+9&>(x2k$d;MA zftkD7$EAcdGSOK&ZuNzbj!(Xfj7AfvIkc4Dpa0jl-wYXox4#z76kMW|gb-8%66VLN zRi>YS^3lriJk1CB;dIw%ldxq7ugrw_j1NrqDdi?p99g8=ippzD=mxJ%n0FE*RYVF&SaaWrR!+NS2<+@b$I-&!mMfz4woK2LGB>U}$CjZ^H-vdVB2XV=~VdY)k12 zw#E40-!5SAS3=y-NyygP`U^`aZEj=y#o%=L0`vb@Ry;{r=ZnIR$_oM$LERi`U9~fd za8%&5!Ivf|4moEgSdKJ5IAV;oVy%|~D^%dyzmreTGB-%D8^g4%5i6hE_^5xf&FOj5 z=6Jll^~2Zq54c?@2?*5X=_)H?U-UP;nWSeycTPNaJTN>FfZNb7Z4Kkl|Jp$pju+yl5UBYW;E)VmT@;g-%_`CvTp~15w+_yF$t@ZGWdei zznQ|^h(Xu8&i!Y5H~Vbeh+V}QS2k%#K=q}bIa~fXgcAW()j@5z1!lXTbSaGJ0s(YJ zkBbUHgW50apGy+`z1Dz9=anR3sNWK)9OC=<@L1vOsfd8ZPBOq1sc`Vbr0`M@^QFC< zY=$6Q9@6_Emw=FqQ}CFyvD ziHpGq%|?|ZF$-3-4_e)Zyt`R|rZy6AhpJrgAt_bHJdvfMw9VSC?d_103(8k=^9xC= zn2v+uU?V@l9Qlzx{G<4V{LnIpXQ+YS1CU*pGNh{4@^{GAUrUkcr*Ta2*AhJadi_U6 z_pg=vw4O|1VoMI8xxRVHhHxW=)zfE=TR5mO?Y~sGuT!D1$DgwC2DPzwGFE z{6dhGwA=0Y9)1v*Y+ro10gobvKne*5om$y6?^;)-){e?-Na|c$PLZ4AZ8($M#DBG; zf>>%7=e=7?i9{wrMRiG~l{9D(f=l}+?ny}ep{|+@Q%O$IctmG$f)YAm2St1NB!!>i z66c&CUZ$sDJHU}%;Fz8{Z&B}Xzi89E{-aVU3PRzPer>zwF9VwAzi+$${#|9v9UaYW zOy%tUYRm;3Ouz1HI2r#dh>@ftx1fhQ@;SrSUUgxVOW4hXk=R9O80zOo7C_G^%hIP8 zpoUPH#=506kG@0Sc{hynL8vR9Pjc6fup7dZT|(=KBQ=~%cEu_8#Rgqz{09dwHwX=l z@la(@AvA%7bg6<0IW^f-gmOxl5od@Md}!SnPD+?@gyC^DA;)fG?g}oYgDdcfA972V zx$WQ-en5MzzGA3+in>^LlQC*PU8TZaff7gX`GYK)KbdkooCx+AeL%38fZry8sNO_{ zB1gcywOegt`KrWgqcCBwzG+}e#s)_b0iNU&C!X%46=+CuL z_k4=>nTd^H@)$JHd=eTURJ>=a5g3AkgTY*=4aLEp-s=RAMv4hkzW#X%)h|y$XP$i8 z<_AbWyD&)O8*=y*7y=wyh057)b(B){P3n2+BhQ-*YKmb}h_PnG<=C-CqLbdF_$+|1JcK;u_(T&A4Lf-Q?9Ha7dUIbaTqafy%|Ji>{bQ zg3T5Il^?=53wN`+K_4pmTJ2N7MF%i*T-cjQ0Z$8s%V80wefrzsfdrRvCEi%K%Hh*p z;jr0_0g;m?6(WvdFyQ;yfhY^2MsvbtG2p?gr|1eTyHqA|lP#tMQZY{o%q zhV+U;tq*#Sq1rDp&-^G%_4Kzy{$IPjvr>lXFX(gN*Mo=mf3pMrx%8F3+$kH=ul-@C z|3!iRuTd~r$}?XOXw*L@?Z*1ndG`f+O#zyD@S>u?z^$JK!*~;RfHtyD66{K}!n`r|pdAZugrF+wI4Z8TuU{)Vs-E#A7&2i@Ca7 z!#O_yX;Ug!vP!y@!bo_u3c#QCY*a6UC^_ec-t4%|mH}p&_=v|6PqpSYjzSY9lcv38 zKObVY@^6Y#u#kCE@tZ8rdFLk(Ij*RBPcLE~q*i<(gB5$48Y`Hc7RVM2`!2xRt?*X! zWIC-(KR!%Jb$SgQrogojkn%Hpo}{o`|f!{%~O~&iKu7=6EXSX_v=u6d(h@55EkIc zcmPR2DXjh%efbps`Y{1%OMCEYvEp#|#-qhDOqmVq){>0`o+P2a5wKicwPE0%`_E|O zJ*|#tTh4CS!?Gn_`~`2s@`D0UvRD&wos+3r`vLQI#Y$TA0XO<4O#G)FsC3K?2kbum z7l0YI$y-(*6R%*P=sr5H2AnpOREPe0nvuCC;?GvlPigoO$SM%3Q6=%X95WC*oCA4B zk$d5gA4H<|dL?#W_XfVTKOt#adEVV@wI}liGUjEGOlz$<9%U0%H+2hoPS7FIBYNa6 znS15@5$Y9VoE(WgI!HZ28GVpIeMMDuC;Q&GY=e!Yxc$9YFrK?r2%p@cVsw{oZh|<( z^U%Buk2pIjw5L!y(#j$47VnMtgN|YK{Ph>JwO)xvJcVq?RD5EFDg!I4!k$b$Pkh3! zWace!C+Oiaz36*J9{9V4R2XY~LnCHX0>N(uhrEOli-cQx-{fLn!Tw#7osz)ai+yQQ zsjv0^KZvZqRr{~X?ri-v=jdw+hVeg2{Xa{5Qrwgz2qS8++#7R2Q?sTu^?CnwqYA`I zQb1?`Lu+l(Zzt^HxHMrV=5xz7s@B_YUKETHQfRaR8k>jNo4>bE?*H7qf$zdjAw#b+ z>2HW^i3Ij?o&=Qe2E66@$`O=dC?I)!DBB~s!~j5pr8rGMRdQflGKNTs!JSaei;gG! zM1{&*$4(50vTlQR2UDmX1w}@X9s)u=Q<$R5FmXnMp&L@e9?v=T5GYgfrFMsDB+Sgp zdbAN6Q`~+R!;(qzrM)iOG3ILIOZ+NA11;*FRPL>AyW4o1SS|aOs1Rf*7@YeMqlR?p zGu1O8JZ{+BDh%WP0b_E+KM9Jbll99fd!b|DT3SH&@*_N&w1Um~2wwG98Dqu#TY3=( zg`o~JYz33))+v^ISI5=#9co)nE$>#Ntv*CUJ=kQ{z+_oCoTdGO%L?D$4D!D!&<_l= z&yg>vXUG?S?0;2(q-?&%aQ(9k{ZoPb?IFCSZs~@4gyz#@l%PQd0tg23S1n18HF8Aw z7BSmMlwSzo$B~X~T-SG%24`p9NLvHF9Fo+!+R~k6Q}wKaT=(#oL>BB^)UD0`wVW^a z`Q-k__esV>*3D+Oi+b56VgsC()&IA8=l|2 zRE9mgcq2WHtp>cAzHmABV=YEsAw>QD8^9g!@a9kdn*)f>clrL9mlqC#AA|>(_=&fM zSkATmFHpYZ?(P(kIrq={STVb%(!U>|-+`Z{UPvq4mHTVH+;aKKLQC~yKTDkB(4d5% zwUppI>l*WO7db&k+$|)ttA@+CWhAW#^2cTMezU8(;b9i$ZPU~@pD7oo4zdj25dO9r zf!*^jC;^YK4pN2~y561gU;}1Y(WE`AT8dqGt2YWbCef_o<}!^3o3pl5;Y)IocSw_c z73&dp*fQUM{h8H}AESYV%{W;fD@?TO+wUNg=tl78YXkf1(!Iq-oj=FjVA5R+3~(({ zyMo^K_r{`mttS!O9`4gtn5dAy)a~z=(WK{du(=C2AR2|B`i0*rQoL$+VPuU{cYj-u24POJ`>-N80I1=n1@~ zD{lcTDhc|=Gi%-YBN4t)M5q`T{@tBVgforI_ARNscoOT*#)tX733Od9_qB0vJtzYT zJrLFL=reZA%;SrDGffUN=%-w@KDgGnJaW+G$&mVu=B9#8)0fi~XqP;(K2<9D(2*%9F0~KUs^ujvXN%2wyro9%rKRgdl_})~ ztR2A*d%lBbJg&--(fXZT3AP(bFu4OP|E#Uz@)**s0xULXLC3{H>nI$1-s)}rH47`G zC0b-zYL37is#Y*|YBzKt=xGD-{>0wqe7YS=4tV3F98RaBTwi6h!G@`Y?@c>zgaE44 zK2E3x=xIaQK1Vmv{s(B?-Uw))k-pRQ49QmTB@5B^0@>5%ZlhQFy=UYgb2K4#oR`b0XZYJG@?suSZvFW7#qf)uou@3OuJr>uan6)s^#W3MDMoC^TUOqdfs z7GuzR`?v3RF`2O}uA9@ASYa-dF4)rFisD^rsXISfp}BdKLwDKI@Z@EXT$h$qr0Aw> zVgY6~dAj|$s{p!Lki4Q>VL_~M?lGrz$*`vE7_Y`C13e|ZeWV;DpLe?DAk>ZO(yp25 zPt$A`YrIm+NmxM8ZVr1z_qur4Fk|LtC6CR((e!^BkD|aLczcz*qwO)yHal6snW7EY zFe4k|-T?1;E{0^KXM9O$o3%)hh>vDR45m@&ab7gWL#4;Jn?m3j+osmuLOzQ2qx*NS zd&S{aZG2_|Mjx>z8M->hOr{12Vb7}3&rhVVr2d!#7F%*mn?xBmBY`c+(&uDFK8}5T zc5x={hTlH&(KzJ@T&kQJG?aQ#9kkgeomCQE)FPh^F0+`CP1>c)3pca}vp!Uz5&!VadS3ptc+!4pn(hshS6&?=cJSmlNrwGZ`(Btt%~&i7XAESK=Bq)sDKAjJ*6YroPp(7F3*67KEJ95&V(7{bm`?*LIl z?tslgLdp$WC85;ByFs0tAuP#J{m10=mK~ScFRJctz48b7A;)a4Aq>>gFTM!4Bo|Ln zuGx!Z_T(s~X{Q@y#E;Cq9#ZQ3wop7XOzx0WwM2TC0!(Tw-*7TY6dcb#d|wSL=yL;e z;~iIdLbxPH-oyu3@@cHGqvFU2Oaudp1v+W}bUUM4Ye2IVqBTB0$b?oOEdKudE}xCi z?~Do_3ZqVokY?qOl3C097tV3t1;q{~g|- zv97F*OIe2v#NI?8mEf-N`tFQLqih#(spU3TU0xr0Um%!rHV|6LE<{ZqHcD^t0s>I7 z$G~?^s=?X3hglsQ8ME+=Li7jc64Tfl%BF;#YCv6Xr^f%&oEtDAc>i}g>0b%~*Al&M z>Pxe2e0AyU|N9>0Kf9A86>GT#c~l>pl{p12Ok1T}Fkx66TVp>$Sfoq?bPR?xqB}#Z zDVj6Qd6&VQJ%qi8pS|J+FPAaI*&A$*wUT>6o-@2BoHK3DosGWLJ|E!nz0r{@$3~C$ zY6xEmyHUhC3PY#~WL-)1xVZh_sJasB2e-~pB2YNUy39`2n8|zAOy3<|y}|2@2+m=> z(FW#SGonDYUMulZ3$aR9S*JEva073IicY;*wE>gcb*8JTV>Y;!(Z_4>fr`yETa;<( z7w19h<*TwfRov@&WfiOo@=zPIl|uN(3CFGN%pgH_h3a9T`qw$T^lbJ;J-|k?z z#qP=5>4>hTT}qdSUK3aaT!7s+8!ZeJ`yT-p(HjR zII|5Ry-H3E4!(z_S#xbQ+&U4A33iIh&1mPFCTBBKl*FmwBKTG5nM)&bJ`_pB2f@Bz z1`k>`LhDbfJ5ASr%N0re-jBP6GWz?)eDdXj27i`Iz+GC2vOaMQqmej)I7;$Iy~3M7 znflP_&wuZ14h@#b_r7+B&6ke;Yi#dd({umkHvg@&`Il#>Vq^LLqoTW1CKSK=cwXMI z@A^#qkcfA|p%i0?kb8N=p~OW+DyW*l^?EDcl@3mg0O`_+KW~3(i=)L*Vfgrc?hdc? zN8zxy*LL!zK_dy0+V352UvE5Tc6(n>9e&)j+yEWgYYD&}Kyxw72~=bEBe_nrcaQYi=OX3z!F_!!;U4HzJtwZY_d66W?Qa<;|;-tr~~#2Ci&#*qH_r2z!uoL zM8J8yGwV17Jcmq|vHB-u)dRq9i3+coo^R)^ckNmA;O{&o{Q?V?CL&%vzx=4*fD!=&wsGgy1Q)K);ID}O6#Ul>K9{)YJOXoSk4UB zn!QQO|0px$MA?s`@|Yu^>14;ZRn4%zAhT^}uB294X5{6RNTXOkPWO%?0GBS z|DyvX_88NQ|MI`m;DG*fti*pf-v8`6|K<0pYB?^bBK}FloTw8)h8^6)C-gH!*+d6U zER=_A;TQKSln}}lsZ$YCj?>mSp8wtT0n_GU=zWco(_CbaG&rfRfS zFIAinHl4kcy=Cj#!+THQ`}v0BmvU!~%gsP)$Q8{d?X1VWecB6a68u=~Hc4s+@&L6t zNPFidHM{|}37|3*5ao-WJ}(Q1Wg+S!Da?1H8O|NC6QkIP>O(9iwO>X>kP?*`_Cz+S zI6O_(NSbjj~F0#7gV@1 zFqUA>I)fm^Uq`IPW?ghhBg?>cCc(02QkWfg*K+L>7YA%9Y0OGCC#waIRa#^$N`_vT zbl3A|t%|clwwq}s%~xL2NOEjKuGdGj-M~S-6`woqhOx z)0VGR8mY%(-KROf?9aw9tgOk4krI^iwrlPMnWc$A$D6EPQ(Yv|UCbO)0k#%L2%>1L zQCK0iGEkt+d1@umr@$kg?$tbZ;Bk_TwUiP2yqLp=lgZ>lGjBUPE(V^ zke-Q_pzVyZQ$a2ixYN9ARztyAZ4GOlH1e|fK*f=1D5IaM(zGI0dissp93jUgOn8pCruCSxt?;P=drityM$Kr zjp%#%nz}})VBB?5q8k2zUC>XVVxneL(AWFfk-wqw^`!fLzwisX8p;`YmF|*wO~Nm0 z$6OdUb1&s@KtJjC1jyZ%1%L>?!RACr>Y7mF*-CZ5xiq;RY9^oVnS{q%*1kXWe2v)!$U{l&KpI1fU#adt7SpA z4C2(z^FvOi-4V$4c(tPpG~JtvhR-l5vU}`3k%f;KH?cOoakk(%kI=@ZQsWPS?+?t& zyaJ8IaSha=60Ht)V0g&*1~|d3gAZY0lk}mm)%@lImj%9m-om!m;|?4V47{!jKT+Ff zBk+&wGt>cLM?6K0nw`QCu(W`<-J7%zp0wi(HM`W8P-rm*(AIfbT+(~PM6_y^Df=Ha zYLr>IDHf`n*FjTU->&K`V;v(X5`S;zs*qoF0;!ObEOr4BvJ)(|!5}#k(30FPPtaVK zKzK6kb$%&Nc~#c0q+zS(ojuoOI}CJAmq0p3V(Tx&t%VL+HzX#lqLmu0cB1 z_@gt-sZH*FY!}xfNDnq%;|?`s7vXZE4V*awM&G2=r|S|@&fKRf+k9G-O0{=M{Hp2f z(ogNe?ilGH+Z}@I9wh!d22MBwda+}p=DFzDXSIbu^Civt{KVnmFh1|_IBjzZeGKn5 zUh20r_C5bt+9*n);Iz#`8g?(qZ?|D@e6SMwH4bmhgfoz87CfD{yV`9BC-O~Cqj|@C zcQgqo^3K-gdkbU(6R-`6Rx1ef`p|yU=_*NRmJTgEgaLlv9|-Sq+z_vq@2qojCh@0L93ZdWb-&J3X=hd%OV2^>f?#8ec*C zICmV^fLA5O;;|^~R2w{RBG&QtW0}e`nN$zjFGCT{z;1cKUjDk*;f_0^JsLP|K@s$Y z67_UQzxLgsO46j4IF7=R()Y;Q+5sTI*16HuK>Jn@b0wqlpAeZS%{zYAp`u19%I*W) zRVc^QubIHXduDGc45+#~?N>bEba1cE*eRIf31a#bGQ@499{qHeDZz%O#A>|lU_nNV zCQrV7uM+-&Yl%kQpZ*A_C!(M)FOaQ`Y&3mXT`kX`gQy4Uk#xy>Hr;$TzJwp*B z{o*`Ps&&c9RVD9m76{|Ur&jvYzv9Sg5Pism^`HL3>eq6!Nlp9`!y`!l<0Jha|G3fL zWbwcJeYG`T95u8*q8p?0!gVSm(OYCH3mnPDoPh7MZeeZp4uYrtZ|;I(pLC=oBL_PJbIrdoPjBpV0?scRng+ zon*2;tYI~K_3A!4Kwd(_7xtZT{0QA?gBfm8r(RM#-L&|&V=vJ`S0-!AG6fDs?#sP^4qcN;bF<7SyEFyNM|HPeF`|;X8{Gm#}?71OKJ8& zNSdozN#V7GFNy3s7Bs<|O4dN0cHuO$^+iXW18ia>PA8WsGizC;cz@1|zDB2uzdTv}lt)gKQdw9Yj_NNggUJs}AaG(*y^A$gD}}tSaA~uUI2i6 zDKTFL&0Sa-tkh^aZZd2)yNxD$`RF$X3%O&1(jh-<)zsJx&esF_oxx{!OWkRUK*13N zf_$y<$z`K)UQejrCw8lVrH0lS=xXex*7r4G4D3fQ0;$pUF*|cJt19Z(EwkO~mKQuR_pM0164WKdsfq%%W7e3EtZKQm?QBWg zVTLPdfsWo&nBOA9bfc|bd>~b{&X5w+cl?T}LpDZoqD(aBIGb#R%z<{-raEQp^y2Gr zDfSx&b%(dfSS^UGDm=zakpl+TuXwd>BUGrt*^~(iPPIzs|k_sIFw|n&9s4?gaPX?jg7)xVyW%yK9i( z?i$>KySo#DLjwQJH<_0ik~eSaKUKHRy;Zl@YCGM1cJE%Bz&*mx{Gx=6*9yD7IBeeu zw1*JV93eFiwv}>!9>boJazt@!e#=FccEdS`L7Q6MT8aSSvx+H&m#|ngIvxU)pJu>k zqVk3P$9uxLAhy!)Ux-NWc*BW?y1iMv8T3zEV_U$eoN#iQG$7ab{dMDYkHNPc7-g#h zbT`3cznQ%zmEN1Hl<87R&x?-ztYV(k6rp@r3nvC{N!%9bMhYv|lAEb5Djuo(U`>0& zORQB@K7j4$Z{xT#)}egb>Xb3&&)Bv1g3{fQc^mI**_uF69N(xD)3zI9$OA)|R%I0q z{ibKDKdd&n#QRq^=`R+m{E{4T z>IxC!e&3!p@4%L|deYjQ>rnD}ekh}3&ua70jCbWBV`yIpmy>i~BoQsis0>ekyqnY` z-N7*(Ro2!hm6iMzI;c?uip9tc<_2l{1JJxP z!!fCj+$z|$#H*d1Lz#9k5D(BLg*l>?I(U<;!766_)=*=82S6iqmv};RiRXSOw0u() zM0%WG(RQhtJcd-81`p+zYryQea(#DtxuH;cbKi`{opo*U4VFyXQTr=eu75zJu@F}a zyYQZR;e}WC*wTKA)L9tE%rZUnHM;9l?AwaPS`-~0nF9HCehLg+3g(yZGK608QPr0k zw;}FaB;O=s+b4tM=1Q>D3m27><>F`Ki1^4wh^|oLUsZpAL==rNxYAsU?qrgBc%uc3 zwk7~3(l5XOv?9dP3VLd}ZXg=3wVGn=!NOR1YBdC{T=OGchH4zSrHk$DN?qC=OdB^5HbO zq?k8C+=+}9L9ifDPm(I+FTcKcq})O+W}Fw^qCGuG=UlsVEU-GGuK>EP(%?tOaWUVm zN)V$8I>B{>TtGpB_7D{Z98&g|R%lMITt+79zz5eVw^W;BA5)j&8tKrJ6uy`r+0Vp8 zj8uQ)QeT*SWVdu4u%i6MhkH>xi-ztto665Sz_Qj%A&^AR)ek5EeQHiH&_BYo z?5b0AI&dgl~zAvE4Sf=zh5?Cz-ROtFoWy* z=mj5P&Ec2f1fgEF#-YeybjpW~_4-n$U@R@gB0hZVgvT#)L=OZ7t=a5@j6ski%*z^R!c!y)^oz^N%BG*BPzoG6N}p1B|~nXA-G0*sfS z027SpuWkC5Cj7Bb7t*ma0E8nmu(UI=H*qxhy)RO}!XFN?%)YQ3R}^ey@K zx`Yvux`L#pf>Spyz|##EEF6;n;XA$7dy`Uc-??9lVV%+9NW*}m2qnAijWz9!@i<-Y zRamXPVEOzC1VoNW{D91dr!5{%(Z10%+j70g$0JZ*ij$f{H#mWY0`|O~J02ECYiGb| zO5&|+f3B4R>6!yX7cWxgD^o^u(Wu?|lW@GbxMtq& z>rjY1h*a%ttk9!<31mNB_Muz^2KaJ^+zbisFj_c*kVBQv^xM|@CNhVFa+_RtySUzP z+c{ZK5}0IBka!en6*KgR+>3YNJ!8@j=L^166-ciFNaaYnnHid}s+IRrkV>xWes22+)=>3^VSTP=y0WvVi zSzI>OXHYsh1xWrw?lkq4d`a#*50#=aWf6&B`O>m8U5gTr6<+hRldDyYB{wU#C08?z zgO2Nzlo4ro^6RawV;;APw%Ns_^Rqgq)vvcrD8ejZa=Vl8_g;5jMR*AY_bRZz(Iq{~ z5*%&kC~%#;n=A@Z+rrJTH__t4I-XtV)=rsaxi(2p>*v}#Q0xqpy?Z6N6~l#g{^8Zw zp6{!d+sxa{>RZGa@SiaWqGn89MNQ3DOp)TBZ87iMfZj=?hDva0b;o&V(vlbr8ij^S zU!AiU4^DZw*Y`2oLo(fr%s{uR>$|X5AG%=G&bEn=9d^!R#%O(b93Vt;32?o9JuAYG8pE*z7G#IeJHJ zjS0_;HUpu@7+h~;_TIu2+O5bULPOq-rNGADq!yc#4>O_{r=?!70-kvp7<{;NR(yI0 zd#;h$zL5D3&Wj?C9_ki zlGgdV57ef0YAq$oX*R}a+5;@qx->1N+=F_G*bZ$AP^rs(?=S!VK;Ntt54JkLdw&Sj2aqeV8IQY^<;Ce;`?JO@akCA8wtBhNLKhu z64Ud!MGsCKhG7Cf)%F<=CEZe?Jz=g8CX1BugVS0bBBSZOfKnOffF0*ojIp z<&B`(?*j+;(}nO3IR^A?JZ&*qNSNaTZ8H%|eP(BLP7PC2Ed&4orW5OOrlRTW7Xzml zw;Ekf$NS^9xNhpj5s~UYkXm?Xk)l?oCiG_rF1{#z#X;UPcpzSHdc?+nw5$+U6;D%b z(22%Kqcq^$;8<55D%S^xTq`{q+^9@N!1U?nMPS&FzMfQu6KP!c@^sq%SDj5IRX$lC<-tdrJV2$nTiCFzAe${O<&yhF9w3xiz0=)rNNc$&( zf>z=7B5Cs7P?1qsJ-Kf%G3_x{EXDA3n@G-))ZWfUk8yz_urJy!&kNRm9I~j~mFCOiC{wDzrDj z0DNr_B4Gce-`TDZkOeFK6q8g?kBLi2Rp@Aff%q^Bhm3OC@cRkz>Gkg1%lVILIIwl% z0F5RG1h2t$w>w_HGYp|uolclv3HkItce@X)(@*h$^L0g;y7#PCKsJ~|zz?zc5o}eE zgAk*1ka@OeY^>MG5BvJ17{b+f<21^KICPL_^}~7kc4RIa0Z(@?l_e@#kKvGC980Bt zl>4H9rBM_iI%>(u#NvtVgG9-;pL9v%7Zv&NT>ZCIf&_@f81 z&FMka!A5vz4ddea-YvM_!<`FcN_p!gFff&}CM^ssFOdmHA65jbr~H104>4JpD9B=c z3K43H79K~bLI8&;z1vYQb3wl$DuiCIAtdDZq}H-8F`;>#VkkO&NJ@vv=LS+7)58&K z4rF>Z4fRkG6Em4oI0~^;i&WL+@V+o7u&t9`W@e6YX-gYbhW@Is=Ty~-H3p(P6yT*N?h$Xp^61S)>30 z)U{2kb$00|t`ACTe6HhkUVhEHMmgw1JQY|d$Fb1HTkFl0_SPwU#30R`{Agd-S<3S+ z?epJ1u>_Rtv&`Q0MWvi{Q~C$COTv3#Waj6g!OsF;DKgj?K-3zV)1Oek<#0xAT(<1P zE?u#?%JyrXz*FWEO^WA2Tf4(Eyismy6WbbG(w7erQfm=drEs)Q$x%fYnA9RvWgIYl zYL#J0pBO#aTwYe4n^h&w4U<&nNZceL-Jhzj{HO!-G;*?Zo?)mjn2f2`HaCLXXU9KV z6NC=ipxUyd)fquohDcChy_#B1C`m>$#Y8dZuD;+B!c5~>wVc{k(0mE3h3k@4pdRPT zhcB_RB896&uwd9Le+B!YVhK%Q0GZ_?u+&?#nij4ZGg{h@7x7dNt`MN!Go9%Ej0F zDC-ahxq3oHL4LmBxD5pFL=B0vEygcORqa@k-A|6eqA$bo1Ho$?dvFH@}zFBuY3ua0|E-@ewY7jxV2Klg`Bu+P)F#vTCHDd@PO1- zzOyL3nB`f|rN~SN-jhv?$~V7ax_CEU`A3c>n#~)4IK*P)S<+`S#-@pGV>LdP)X3O| zq+0DSJY|^6VHJ^y##9k`b_cwduxaV1g%H80B@ig3Y;j@}vb0C}nw3*&u2g53P2;So zLo018o0#W7TY}=`>Vap#l_l??>@$%W`V35&St+J-(P9w)ZP~R6phZ~}g9p)?u zzG?{?i*YoK_;hxZ!x{8p{7lLcqj>xUR_4-r(HPwjTAUJ}@I@8P*I19y4-?F4hfs;G zp*zcg06(bh>0vQrD*HhR6?R51{{n>kB*cRugA43P_g6K!LAwy7nVQs@k#B(3B3L5X zCKP*|>9k}qCeYQTRH8$?mP}|I6qvPS=Ji&`^D1`~$ zKxE`HL}f!p<;!M@b2ldEYD(HUXOgnBA^Q#2whU0=J1p zn==&%Fn4DLykyG&H&NI>x+Fco6&m9UKx-N|ElIr+u(Zrok*t`S%AU&hYnu(q`1WGf z6}iLgj?)#K2O;5tyRwH-;tJP^31Pww^nPQuvqQuZPb^V(=dC;e4@BAx1*51uPgYDB zTTh$!Ba|zn50@PyF;+!F6H$RV5TyBfGma##B*;rTFinUIwQX2k8+z5{y-r<%*l%QM zU@bXKfmJfTIBm7r?X5YR!gWhZCsbchn+OCY(OnGZoIwVciw8eCV(gCJf5Y!NqX+h= z4KB**0!P8ti0O>&^=0tESGV1selP%GxL@ z0WY}KPZBOe_%1?N=Q8*87D#i{pFvW;Xnd=!Eow~~19P7RR!(Al0e0ki&>9$u+I~G@ zo1B|97Ig6PgN+KM*)&i$@-+ulo4-ISTN+HjD0HQ~cyM?J<>0n1gIdVQtoh5P$7RqY zhe;{^+LwfHPCUH}Zari%r^K$_Ir@_$NXucq@;H?5sHoiFDDy@AP^muOF6k0_qcaIT z_pbBg)76oOeTdY)5i#L*wsAa9n4>lnc>W6M0a@)-=qq2jnSK+CI*1J-sh4=IdCYbC z8LBx^vn4x{w<$hd!^`xq72<%6r<)^qYLRf%5yel9Q0m`+jxT9CH?>j8fxEE zJ|y?wNGwz!awY?o;wQ&|Iu?UY5^PxzU!Q@5K{3G*O+6J%l}ubgD3c|;d<1)OZRbDn z0GYH#S5O{jYDPLubaXD7#jq zSoMYCA*$iPJhA28nApIM&x1EU@wSCkv95pUBmEF(;6zjw%qNO`d#82} z#bP~S-VU+2Dr$Lr40c~6$t;n|X>7scl{k5^2c)DiOV40<;Y`xI#gmu+l72YkelN*5 z2SRIFPNg%&u+2^4&TN}bv!=Q1SAg*Z3xk z@0kg|38l(;`B?Dg4)0r05nF~e0Ps(+R^S_hVtpt z3(}4u^r5kfVLEY1-h?R|o6RlStw-`@ZW!IoTGW?McdN~3--^g@RKT6wW)gAf@g;hd zz!^&MHj?88cPi21Gr;bsnWb?Jp#17LtiqbVb;gu2F77bXhmDW~wYqB!zb8lyB@5gU zzGptG+v~V+O<$eYQDEwGKx6-++9wm(q<2NweQZ;orE`9}BEZh!qJmY>EPGjbK}~kY z=)OO5^pvBsS$11-;MkjwGeURtC`;&^(6V$*MBqa$dD%N6SpQUcOci^dJ-|yc3}k{J zpf@eK?Ifp80Z$ zO&A`>0{VP9U^C%g_4z-#`yYD}~p?09m$f*1j{KszeM$>gAX9U?e51C!!O+5&5pOsz@lx7{Ft95-pOO?5nvF&gkr zX|sm=OO|#iI-O=g^?R{3x-E{4Lkt?nJacJO7L25(+CmKabLJ|gXAO@2Pl;z7f{tGf zmlrnM>9#0SAe~OU1=e1}aT`#&?=*K+aK3^BJyAEyT&qpxwc_Y1=B`(|P^UX((+^`` zM(}^j5kc`Hr2z8nyui{%)|^Y&eK@+*H~#{`iY+te0ieJ+j7b;Xtm0IW<0)SjB@=Wk zXpE1Hu;;E?paMC~wfvve&tnfCpk=IFA3U{MYj?)`-Ka;Tf+KY>xok0Gc4H!GVRf~O zjGY02p=e-HR`RpXmSlx)xia4=^}|UsR)$6%B~N~i&#|RCgzW=(y>h$d;xmn;^D(;) zi*Q=LPs2Na7d#F6$j!ZMfO%xslz($MDY(QdDjDb(Z&Zs*^bIZUi7jx< z+?{)ah0v@@r&s7~I>MRZ4mCFuz08*01Yh6`JSq#Ag8Q9N=&0B_T&!4EPRdS`UJrcp zda7?)1_=f8I0N1&e|LDQ&!uIKcVkSK#qxoU=w@?@iBYml$t2?lwMI`~b1de{DUYTI zHoHkc%stHCcOaa}Hx=IjVv1tJ00D9TI;{Vx74vU4K>@mePS{GQkMOY4Oewp*IjrPm z60m5%aAox-bS#on)9(gFib70i9KE4ZPz|jOgN2{`&dPV(-X6hNXiBMMeZzZ%cgXS~ z)UV7}>)O=3UNAbd@_I=3xZSFF>jhyA#wX4Zv_zapmfnlES}NWcCP!9EJX)A_KEVTp zRVty{--pO{euvo3pU8d#@i`w9p&q=F$KS@imx8<1g>aK-?Mb|6j@TCVF|WYv8rhE> zcR?31F(P+}A$G#vp-p+PWY>f2Ex81t9%}rTL|s$&Rb=SvOhZP^Pnd>O+Dr+$Y7rPi z>aP+S%oV8S(+cF3WHc1Y4Bo$yza(aFG2e$ERU8HFR+ZpU=34%+n3}^J9z>GjvBMvd zgw}@oX|bRnN!+0;-&L#*t*jIstH8BX4NSpI4VM=4zFfID1(Xw&7pfj)u|QYdKMZA~ zf=e%OVeTlde!}*O`h=Wm5*06oP#_JuLW6#$+TWMvVv+xc)KT^NrjaXidII# zH5cEKYA3b%O+{+40cSLD+x(@mU5|ZG5eu1c6&!x}JZ#?~QUng3pQ43P3PF0@{+J^{ zdiUY^M$>Frs(rV;RAOUCf~$Z9G3q&-0eeY;eXnEn!xqlPLgCT?)yMK{xPgVHm^Bp` zN8(L%`Juxk7rT7!_a{4>URqt+D)cVJ*0|R-qNB7QoLDovjJ+^Ce3oJXp=~flApcS$^LHK@iA`uDH7h3p&rr-g{p(0zm@P!^!eZ9U8gFah>Uu8`MfJY9G>z zdUSlO*~+l$Oj|+vfKm;2JF5$KJEzNnhJ!i(IA;a~To(swAKJc$L>z_t(SAxD#fKSs zA}VGTQYo9^#IV&l8vW*x!sSLGE6lb74wj7&PbXOC+a!{HU{IktQPYR6RQ*sMn}xI3Lg)!MoTh&`40Am5-cg>*7oGq_R{JCB|(;wrP{@EZ4|eaG+UNBNlOLMJH;4)jMik zbb>1f{Zn5q8F_8)r}_RqaK&Y`qO}6%W^C}{kjv`Bpe-3f8N zCaM4>Mh@LjC`Rk+5>Q4Wlb$?qKPw?_wZQL_G98i^^e6K6+w2g@Xkib)mj0*Xm-(m? z?kO3Z4;g3fYn;p-Pwr1I-XYOY>A!w0FHPhth;Yx4mAexdnkGS;7ww(dM@LI;qv3h> zp^>KJX1h9Xvpp078rf~c+(t#9pnCW^dG(8bk&6Ej9nF3bbhobd@CLMDvuBcT%^L=!n-YCWvL5YHDC8yQy$H~1(eXB*U%qiZ^W16EYLVr%-fDH;#6 zrSs5Nf0Dsp2MnapR0TKaL~0QGK4!)muLhc2wxm3boIP3XD!G4db++-7NU%4HN<&jx zjX8Zo_qB7R+5_hk6YYSY0yVy=GidB9>@EqH@(CuM2^JS`dnolTz2;XdvaIpv!rcl~ z#@A1ME`bJmZb@?=f|AA}15bl#R>9gd3KIfp#Hv;~?+Fv^#Z2C5`9VJowwH8inX$Ba zNKd8>j{D{M48ELn@|Hz{C6;wF5^&wF#U)WnjZb1Yr4}w4iij94wNu;m7 z*F{=3$t=F6MT+mUZu^S_@lo(*k``zBsqoKBj_hNfHe;{iX*VDWxns9avNsaydqYWsbGaVt3-X}~LI0~> zQr{hX#6tZ?PJ)D|XVZ6mXQf>-;3+5uoM!!3dKZ5@1_Dkxwg!JV!2Ct;qD%>}R+UHO zX#lugJ656Pc&TQieiZZIKuvHM=yK(`l(hJ*My^%?U~t(26C)97BzOeobdXj|&9D1fpHOhSpdElN_?QCr%lm)+e(*3OOY1(*Q+oVP4q->RJ4f?f=S-3!p*&=z0$aY2j@s@K8FSF()PF> zLGK)}M0Y_ytay-rC|{Uzp+0)qdfbJpwH$(?Dbkcj|2Uk$bFg53^C2VC>=kZd_yo?I zLMJ28ZrffTd-az*BDt@PqXLbq_XcQ?h$Y!9q|f}Yj%kQRJqae{1n52_exXH(7$xjA zWE8A|-T4rBERy|=l$E&01?v5a3+Ov!H0FX1{=4ywFoANVsxXS#jgSqd^4FOg)P2C2 zJg~L$B3sP4!mRv>9zw@>Oc1Y~@+p|7UB2?Xfe=3iFTbvOUnBuuAIza~I7#PdALe5T zI=95S)yO|fxI^mkc8mszYZ$kj>I+SrP$nW&HP~jG2>QXT)!zqx3Q%!aEP&UZ(RX9S zuLJ*IJiMMc=|#xPNOkigdH{T|rz?snJ`uA(i&n@`iOZt`Lqn}L95kCtuvf3dKe8b_ z0(r$Z%H)8?31TE>xYV4r>Fn;^9N~2GjN#eir6Z@4D!`P(<*dOJnt%KnPqop3S-!xr zBLL-wC+DIITmg;56*cVdg`HB=wJxz>84W< zK+lqvw=DPOpo5WTFHW8s$p@5$4JlBz7z`tzmSZ|MZ?OcgoI550bp{qfNov)8Qt-q4}my znQqsQS7%oAKzTNA`_x7TWE5@dMyE80Jd|Qk*IcIF zoXj2(n>~!EJ#m_rSYM#w&@xWR>cK>b6PTm6P^?~I2AXz3igj`rEQ?J^z~2%)<$@k4 zFhx0EY_LbL&}?GuKE|*exuRNY99NMsxD0Xy!K$PbAzA!Rqgj5?UcI2T$my0G-gBlH z@__5>L<2M4tB`H@Ww6v;1Q~@ka7#{n(>Ut2a2rjwres(#pBJzQYm(G1HL~0XyKYP* zskO$2Sh;P-deJb3-P8)H!MM1mozgH(XzAU-zB;YLHbF!##8|z%EztGnVEswZq4PSy z&JrKi@!NHiZ^G^8oQ;#CB3=$UpMD?%!(bp+^%zFv>~2t}B3l{}Xgy`Da2SobKOK6>1OfWS-!Us6n#-%ZN_ z;yS(TGrj5FxbAW1c>JrGJ7*@7`OHwNLwF<~Gmo=5z)gf8|QL{hb8m&R9R z;?s6wbdK4|9oF6G84QdVlhECaQise&_Jb2r;V-xJ_S`gW+% z>lm%zVrOq)@o#Jj8L`8X-TbJ5pXN&}>gs1rXGE4+1yO{10x+V{G2YO>KJZM%pNb(x zA)gXll~nPCA?OT3rZ7P)66jG&XTJFKG@9Pj>ebr*qVt7$5+Onau{mmvn*S)UIa;;| zq1VtW@=s9ui}%s|^~3$**^5T_tI{h-p>ABZ@o&!T!nlN}RasZ|KLRoy75?5w5Vu^YUs1eH>zg}YT$bso$Oz3PZJ6b4hw1`+|z6U)Z!dEKvq z>a+Di(xTkhv%0L0fQ3`w#MHU0ZlV&dY|5_N7$7@iG@X)biOBJ_pS5U7EBmFW59)Mm zhg~HE=heFWFmT9N?q)T13N=cC-2**8V8;PUl6qIK3f=&X#?EhuhOr%b2K1EFx}g2C z)|;&(Cxzag4s4V*m%GRqaDSPr4qeP^cC7vc`u8TKLo@b;05oR{z*xleYm@$E+Wp~J z{mb$)L1E2m@-^zCEEOt@UFWhQ3ko|f1KXiHGo}n_YI3v%lp%s!3x9WWyoTKp-96vk zW*9l|EyOM0jmu&|-w)JeGLzsY{mgwYeQC|h^AXq@s9zL&iH&J~EZ1}k%aL}Pjs-GB zCG#?O&R?<3Mv#H~<2YHVBhLZ8cKAd2J>@;Go8-zn!mlVfuPo%!4efUW{Fkd<=ajg{ z1ka~D5$2b0wL=OeA!w}dN2JFf3#~bTx9{2eo~sVzlt*tX6;3Yscd5D`w3rv|?<;JB z#a3#cWrj&@+zLjfYv;Y;HSfs#y;DNsR#ne0{$;(RE0l@m zer}jy#?}S&)&@wvY8xFiAm3COE+L(voaSv+zlcV!etWTnLj!L9XjmIJG*Fc(2dr8S zOwKI82u@uk&A*s#V7zZgb%nP(r@@fMI3kK1+u5^-ed>d0jfbGS9z-ehHd1WvIJIi@ z%)*dcVT96$)_7o4VpA%yFRv_R`wME;C2JN-W;SvmPsCP(b&>%V6>pJFZ!PEhzz14_ z8eyUbjK6m)(R#=m9>8j22o2l1`kFpw&c`qcvdbo8BvgvBU zkSd+2lwQ7}R27+co700$2^9x^j^+-u24ZCYI>qMeUU<}|5XY$mG^$iu7sFOojho|* zyOC2(3unip`vW{-pa{}vFv}z|+JtYgc(nk1ptV$IrT3C1Gqqr6vNtzypNO-KoPY2G zFg~O>!*rU}v>@Toc*bOeCaKXti3?fmXh}?7dlivc36+upy8ioYOq*(a71o*7eukMN z^T&z+GHtr5Fo5Q?bL@R_*LWA$g$52y=((cyU|dhuWR0z-1e;tOj$i4hjc}Ev%oCSh_zKEfyyxALF1bRp;PF6vgU(-u4jOIn@+V3hQLc<%+9O z%YO|SE?OiRWNIAF+rr!+%}S6G$9^}>L7WXgMnx_pcMvUJQA(v|_heoq-U%F0Op?>a zXW5!Az@0djqMqhdUm&kSvv{Ten|X+)J=)4~^{8^#MIj)9@oh(ApQqEEmv=nauDf>+ zXY`0|QD$mYMiDxYw;K&Mz-i-A+zn&V#A27LKA7toR69`sVvtK*?3yDg%)56&pBSqf zt$9@Rn>V;~Fu8Bm`aQ7REzP0kt+apH$- zDr_gXjhlPk$DHa5Fts%Xpz}1t8QC5aKHN~6V(d*Hn{axXDEBFW8=)Ga-e00?Nh=tC z&3o5707Y{X+$&<& z;I27-3SG<>UZ%Z>6m3cSTA#(#VNvig3_}WDZNB~cQ`t|UK>Gdkj4q5-Djd$-(`yw0Uy-w|NL>~`Fwv{{7*6&0a*!A5e3DUGNOM)FnmS~nC*UC zh(O;2QUTw;%OU+!3czdAUsAOHNby5J>7Nn;BuoF2@Zj$We*|Foh1u9w-|#))uQPxT z$&cjUZ?!(Sfc4j3q?`T(_%kZhpGxb`bJV*UFpCP9=dA%H|K9w6UbQ~o8{?kpN!`0S9|yfcCA4-cMoY=Zt&Ds&>TynkB%8_)j{(z1GJOkcZzeO8i-dKVCAQ;UJ=L`}>M1oZR_?CfN8EOm_jjI{7P?;~*9qTdy??*R6^ zzvmqf5YqHF1S)?7<^Y&F3{8w20P@)YDf!>$UETxFWCu_sSb)4U{F!mUz1HUr@wd>= z+fy76EW-T1Wjx=7p;;M_aUekLd>=9XylQ=D(SM6BZDsUdYhldxC=?CgXJQE`^Y>#> ze_pjd9e@xZzo~`41-<)y@i|uOS8xE2*fC%fAp8>$aIf{j1pNFBCg4z@y^gv0@6lE| zr9*lEm6-q-nEu{QHh_Nd8#F;HEBo(ZH>_oJOe_I5*Pp>Qo_7K9*#^=nKpT+&iuyfd z!k<^Ik2fIX-*2FQbO8}(13icTsHZ{gaq7ceNu#ei~7ey@dNvN zrhi`%&m->s30>-`XC!pcG{~iM8Im-7`-yg~E*Y~h7e_Y?^ zF;kttmGZlU&GW?1gUI}u9MEBYP5h^uz+aZ@&y26n;hzTv`3X;||4ZKf0{i3TlJVEx z_`Tkqhv4{$ifHgJQ2!wy$MgI@4|VX93EB8xF#Xk02v9ofzXm>dPW4=A{wLL==|55Z z7v1^i@XsaFf5OL`{}cR=miQ+D_2*R2wO4;q)!Y9Isy{02e^u9W`O2Rp^G^SQbIj*jQ$I0}z5faGM}zzU{l|~LZ0 z)pMWPpO~jP{~Pl+9=6Xj{oH}^C&6akzaaSQDE0diJa-@aNzhyP3&EeW#J@58J}=30 zcaNV$o~6GK{nB0k>2Z6$h5i%Szv36VM3o&v)#8 z!p_zF6YP%x;~)0#o)_S`UH>N&PUA03&)*;ZQg{EOK+kQ>KWR>zexdmb?2iKdar=j{ z@43tAD1qOHv{2pC~5eN|=J*VCI{n!5k DXC5Z7 diff --git a/python/educational-python/gradle/wrapper/gradle-wrapper.properties b/python/educational-python/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 0f29a88b187b..000000000000 --- a/python/educational-python/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Apr 29 19:52:53 MSK 2016 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-bin.zip diff --git a/python/educational-python/gradlew b/python/educational-python/gradlew deleted file mode 100755 index 9d82f7891513..000000000000 --- a/python/educational-python/gradlew +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/python/educational-python/gradlew.bat b/python/educational-python/gradlew.bat deleted file mode 100644 index 8a0b282aa688..000000000000 --- a/python/educational-python/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/python/educational-python/main_pycharm_edu.iml b/python/educational-python/main_pycharm_edu.iml index 4f0645b39f2e..240ed9eb7444 100644 --- a/python/educational-python/main_pycharm_edu.iml +++ b/python/educational-python/main_pycharm_edu.iml @@ -15,7 +15,6 @@ - diff --git a/python/educational-python/resources/fileTemplates/internal/task.py.ft b/python/educational-python/resources/fileTemplates/internal/task.py.ft deleted file mode 100644 index f0a4bcecdd24..000000000000 --- a/python/educational-python/resources/fileTemplates/internal/task.py.ft +++ /dev/null @@ -1 +0,0 @@ -# TODO: type solution here diff --git a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft deleted file mode 100644 index 16ba2d37f90c..000000000000 --- a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft +++ /dev/null @@ -1,224 +0,0 @@ -import sys - - -def get_file_text(path): - """ Returns file text by path""" - file_io = open(path, "r") - text = file_io.read() - file_io.close() - return text - - -def get_file_output(encoding="utf-8", path=sys.argv[-1], arg_string=""): - """ - Returns answer file output - :param encoding: to decode output in python3 - :param path: path of file to execute - :return: list of strings - """ - import subprocess - - 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())) - - -def test_file_importable(): - """ Tests there is no obvious syntax errors""" - path = sys.argv[-1] - if not path.endswith(".py"): - import os - - 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 - check_importable_path(os.path.join(parent, python_file)) - return - check_importable_path(path) - - -def check_importable_path(path): - """ Checks that file is importable. - Reports failure otherwise. - """ - saved_input = patch_input() - try: - import_file(path) - except: - failed("The file contains syntax errors", test_file_importable.__name__) - return - finally: - revert_input(saved_input) - - passed(test_file_importable.__name__) - - -def patch_input(): - def mock_fun(_m=""): - return "mock" - - if sys.version_info[0] == 3: - import builtins - save_input = builtins.input - builtins.input = mock_fun - return save_input - elif sys.version_info[0] == 2: - import __builtin__ - save_input = __builtin__.raw_input - __builtin__.raw_input = mock_fun - __builtin__.input = mock_fun - return save_input - - -def revert_input(saved_input): - if sys.version_info[0] == 3: - import builtins - builtins.input = saved_input - elif sys.version_info[0] == 2: - import __builtin__ - __builtin__.raw_input = saved_input - __builtin__.input = saved_input - - -def import_file(path): - """ Returns imported file """ - if sys.version_info[0] == 2 or sys.version_info[1] < 3: - import imp - - return imp.load_source("tmp", path) - elif sys.version_info[0] == 3: - import importlib.machinery - - return importlib.machinery.SourceFileLoader("tmp", path).load_module("tmp") - - -def import_task_file(): - """ Returns imported file. - Imports file from which check action was run - """ - path = sys.argv[-1] - return import_file(path) - - -def test_is_not_empty(): - """ - Checks that file is not empty - """ - path = sys.argv[-1] - file_text = get_file_text(path) - - if len(file_text) > 0: - passed() - else: - failed("The file is empty. Please, reload the task and try again.") - - -def test_text_equals(text, error_text): - """ - Checks that answer equals text. - """ - path = sys.argv[-1] - file_text = get_file_text(path) - - if file_text.strip() == text: - passed() - else: - failed(error_text) - - -def test_answer_placeholders_text_deleted(error_text="Don't just delete task text"): - """ - Checks that all answer placeholders are not empty - """ - windows = get_answer_placeholders() - - for window in windows: - if len(window) == 0: - failed(error_text) - return - passed() - - -def set_congratulation_message(message): - """ Overrides default 'Congratulations!' message """ - print("#educational_plugin CONGRATS_MESSAGE " + message) - - -def failed(message="Please, reload the task and try again.", name=None): - """ Reports failure """ - if not name: - name = sys._getframe().f_back.f_code.co_name - print("#educational_plugin " + name + " FAILED + " + message) - - -def passed(name=None): - """ Reports success """ - if not name: - name = sys._getframe().f_back.f_code.co_name - print("#educational_plugin " + name + " test OK") - - -def get_answer_placeholders(): - """ - Returns all answer placeholders text - """ - prefix = "#educational_plugin_window = " - path = sys.argv[-1] - import os - - file_name_without_extension = os.path.splitext(path)[0] - windows_path = file_name_without_extension + "_windows" - windows = [] - f = open(windows_path, "r") - window_text = "" - first = True - for line in f.readlines(): - if line.startswith(prefix): - if not first: - windows.append(window_text.strip()) - else: - first = False - window_text = line[len(prefix):] - else: - window_text += line - - if window_text: - windows.append(window_text.strip()) - - f.close() - 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_not_empty() - test_answer_placeholders_text_deleted() - test_file_importable() diff --git a/python/educational-python/resources/fileTemplates/internal/tests.py.ft b/python/educational-python/resources/fileTemplates/internal/tests.py.ft deleted file mode 100644 index c6e2c659a000..000000000000 --- a/python/educational-python/resources/fileTemplates/internal/tests.py.ft +++ /dev/null @@ -1,17 +0,0 @@ -from test_helper import run_common_tests, failed, passed, get_answer_placeholders - - -def test_answer_placeholders(): - placeholders = get_answer_placeholders() - placeholder = placeholders[0] - if placeholder == "": # TODO: your condition here - passed() - else: - failed() - - -if __name__ == '__main__': - run_common_tests() - # test_answer_placeholders() # TODO: uncomment test call - - diff --git a/python/educational-python/settings.gradle b/python/educational-python/settings.gradle deleted file mode 100644 index 914a13177bf6..000000000000 --- a/python/educational-python/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -include 'Edu-Python' -include "educational-core" -project(":educational-core").projectDir = new File(rootDir, "../educational-core") \ No newline at end of file diff --git a/python/src/com/jetbrains/python/PyDocumentationManager.java b/python/src/com/jetbrains/python/PyDocumentationManager.java deleted file mode 100644 index b09cb4fce69c..000000000000 --- a/python/src/com/jetbrains/python/PyDocumentationManager.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2017 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.python; - -import com.intellij.codeInsight.TargetElementUtil; -import com.intellij.codeInsight.documentation.DocumentationManager; -import com.intellij.openapi.actionSystem.ActionManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.wm.ToolWindowAnchor; -import com.intellij.openapi.wm.ToolWindowType; -import com.intellij.openapi.wm.WindowManager; - -import java.awt.*; - -public class PyDocumentationManager extends DocumentationManager { - - public PyDocumentationManager(Project project, ActionManager manager, TargetElementUtil targetElementUtil) { - super(project, manager, targetElementUtil); - } - - @Override - protected void setToolwindowDefaultState() { - final Rectangle rectangle = WindowManager.getInstance().getIdeFrame(myProject).suggestChildFrameBounds(); - myToolWindow.setDefaultState(ToolWindowAnchor.RIGHT, ToolWindowType.DOCKED, new Rectangle(rectangle.width/4, rectangle.height)); - myToolWindow.setType(ToolWindowType.DOCKED, null); - myToolWindow.setAutoHide(false); - } -}