Moved educational plugin for PyCharm to community (from github cscenter repository)
authorEkaterina Tuzova <Ekaterina.Tuzova@jetbrains.com>
Wed, 13 Aug 2014 14:14:47 +0000 (18:14 +0400)
committerEkaterina Tuzova <Ekaterina.Tuzova@jetbrains.com>
Wed, 13 Aug 2014 14:14:47 +0000 (18:14 +0400)
Merge remote-tracking branch 'educational/master'

238 files changed:
1  2 
python/edu/gen/icons/StudyIcons.java
python/edu/resources/com/jetbrains/python/edu/introduction_course/OVERVIEW
python/edu/resources/com/jetbrains/python/edu/introduction_course/course.json
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson1.task1.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson1.task2.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson2.task1.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson2.task2.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson2.task3.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson2.task4.1.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson2.task4.2.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson2.task5.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson2.task6.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson2.task7.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task1.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task10.1.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task10.2.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task2.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task3.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task4.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task5.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task6.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task7.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task8.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/hints/lesson3.task9.docs
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson1/task1/hello_world.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson1/task1/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson1/task1/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson1/task2/comments.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson1/task2/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson1/task2/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson10/task1/input.txt
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson10/task1/input1.txt
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson10/task1/read_file.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson10/task1/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson10/task1/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson10/task2/output.txt
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson10/task2/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson10/task2/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson10/task2/write_to_file.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task1/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task1/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task1/variable_definition.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task2/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task2/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task2/undefined_variable.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task3/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task3/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task3/variable_type.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task4/arithmetic_operators.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task4/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task4/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task5/assignments.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task5/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task5/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task6/boolean_operators.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task6/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task6/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task7/comparison_operators.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task7/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson2/task7/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task1/concatenation.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task1/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task1/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task10/string_formatting.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task10/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task10/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task2/string_multiplication.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task2/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task2/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task3/string_indexing.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task3/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task3/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task4/negative_indexing.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task4/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task4/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task5/slicing.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task5/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task5/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task6/in_operator.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task6/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task6/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task7/len_function.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task7/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task7/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task8/character_escaping.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task8/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task8/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task9/string_methods.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task9/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson3/task9/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task1/lists.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task1/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task1/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task2/list_operations.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task2/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task2/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task3/list_items.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task3/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task3/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task4/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task4/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task4/tuples.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task5/dicts.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task5/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task5/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task6/dict_key_value.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task6/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task6/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task7/in_keyword.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task7/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson4/task7/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task1/boolean_operators.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task1/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task1/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task2/boolean_order.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task2/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task2/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task3/if_statement.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task3/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task3/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task4/else_elif.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task4/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson5/task4/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task1/for_loop.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task1/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task1/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task2/for_string.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task2/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task2/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task3/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task3/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task3/while_loop.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task4/break_keyword.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task4/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task4/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task5/continue_keyword.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task5/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson6/task5/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task1/functions.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task1/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task1/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task2/param_args.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task2/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task2/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task3/return_keyword.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task3/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task3/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task4/default_parameter.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task4/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson7/task4/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task1/class_definition.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task1/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task1/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task2/access_variable.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task2/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task2/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task3/access_variable.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task3/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task3/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task4/self_parameter.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task4/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task4/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task5/init_method.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task5/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson8/task5/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task1/calculator.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task1/imports.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task1/my_module.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task1/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task1/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task2/builtin_modules.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task2/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task2/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task3/calculator.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task3/from_import.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task3/my_module.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task3/task.html
python/edu/resources/com/jetbrains/python/edu/introduction_course/lesson9/task3/tests.py
python/edu/resources/com/jetbrains/python/edu/introduction_course/test_helper.py
python/edu/resources/com/jetbrains/python/edu/user_tester.py
python/edu/resources/icons.com.jetbrains.python.edu/Run.png
python/edu/resources/icons.com.jetbrains.python.edu/ShortcutReminder.png
python/edu/resources/icons.com.jetbrains.python.edu/WatchInput.png
python/edu/resources/icons.com.jetbrains.python.edu/add.png
python/edu/resources/icons.com.jetbrains.python.edu/checked.png
python/edu/resources/icons.com.jetbrains.python.edu/failed.png
python/edu/resources/icons.com.jetbrains.python.edu/fatalError.png
python/edu/resources/icons.com.jetbrains.python.edu/icon.jpg
python/edu/resources/icons.com.jetbrains.python.edu/next.png
python/edu/resources/icons.com.jetbrains.python.edu/playground.png
python/edu/resources/icons.com.jetbrains.python.edu/prev.png
python/edu/resources/icons.com.jetbrains.python.edu/refresh.png
python/edu/resources/icons.com.jetbrains.python.edu/refresh24.png
python/edu/resources/icons.com.jetbrains.python.edu/resolve.png
python/edu/resources/icons.com.jetbrains.python.edu/resolve_dark.png
python/edu/resources/icons.com.jetbrains.python.edu/showHint.png
python/edu/resources/icons.com.jetbrains.python.edu/unchecked.png
python/edu/src/META-INF/plugin.xml
python/edu/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java
python/edu/src/com/jetbrains/python/edu/StudyDocumentListener.java
python/edu/src/com/jetbrains/python/edu/StudyEditorFactoryListener.java
python/edu/src/com/jetbrains/python/edu/StudyHighlightErrorFilter.java
python/edu/src/com/jetbrains/python/edu/StudyInstructionPainter.java
python/edu/src/com/jetbrains/python/edu/StudyResourceManger.java
python/edu/src/com/jetbrains/python/edu/StudyTaskManager.java
python/edu/src/com/jetbrains/python/edu/StudyUtils.java
python/edu/src/com/jetbrains/python/edu/actions/CheckAction.java
python/edu/src/com/jetbrains/python/edu/actions/NextTaskAction.java
python/edu/src/com/jetbrains/python/edu/actions/NextWindowAction.java
python/edu/src/com/jetbrains/python/edu/actions/PrevWindowAction.java
python/edu/src/com/jetbrains/python/edu/actions/PreviousTaskAction.java
python/edu/src/com/jetbrains/python/edu/actions/RefreshTaskAction.java
python/edu/src/com/jetbrains/python/edu/actions/ShowHintAction.java
python/edu/src/com/jetbrains/python/edu/actions/StudyRunAction.java
python/edu/src/com/jetbrains/python/edu/actions/TaskNavigationAction.java
python/edu/src/com/jetbrains/python/edu/actions/WatchInputAction.java
python/edu/src/com/jetbrains/python/edu/course/Course.java
python/edu/src/com/jetbrains/python/edu/course/CourseInfo.java
python/edu/src/com/jetbrains/python/edu/course/Lesson.java
python/edu/src/com/jetbrains/python/edu/course/LessonInfo.java
python/edu/src/com/jetbrains/python/edu/course/Stateful.java
python/edu/src/com/jetbrains/python/edu/course/StudyStatus.java
python/edu/src/com/jetbrains/python/edu/course/Task.java
python/edu/src/com/jetbrains/python/edu/course/TaskFile.java
python/edu/src/com/jetbrains/python/edu/course/TaskWindow.java
python/edu/src/com/jetbrains/python/edu/course/UserTest.java
python/edu/src/com/jetbrains/python/edu/editor/StudyEditor.java
python/edu/src/com/jetbrains/python/edu/editor/StudyFileEditorProvider.java
python/edu/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java
python/edu/src/com/jetbrains/python/edu/projectView/StudyTreeStructureProvider.java
python/edu/src/com/jetbrains/python/edu/ui/StudyCondition.java
python/edu/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form
python/edu/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java
python/edu/src/com/jetbrains/python/edu/ui/StudyProgressBar.java
python/edu/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java
python/edu/src/com/jetbrains/python/edu/ui/TestContentPanel.java
python/edu/testData/course.json
python/edu/tests/JsonParserTest.java

index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a212ebde3b8325698fdb72cc38834a8f565e17fe
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,26 @@@
++package icons;
++
++import com.intellij.openapi.util.IconLoader;
++
++import javax.swing.*;
++
++public class StudyIcons {
++  private static Icon load(String path) {
++    return IconLoader.getIcon(path, StudyIcons.class);
++  }
++
++  public static final Icon Resolve = load("/icons/resolve.png"); // 24*24
++  public static final Icon UncheckedTask = load("/icons/com.jetbrains.python.edu/unchecked.png");
++  public static final Icon CheckedTask = load("/icons/checked.png");
++  public static final Icon FailedTask = load("/icons/failed.png");
++  public static final Icon Prev = load("/icons/prev.png");
++  public static final Icon Next = load("/icons/next.png");
++  public static final Icon Run = load("/icons/Run.png");
++  public static final Icon ShortcutReminder = load("/icons/ShortcutReminder.png");
++  public static final Icon WatchInput = load("/icons/com.jetbrains.python.edu/WatchInput.png");
++  public static final Icon Refresh24 = load("/icons/refresh24.png");
++  public static final Icon Refresh = load("/icons/refresh.png");
++  public static final Icon Playground = load("/icons/playground.png");
++  public static final Icon ErrorIcon = load("/icons/fatalError.png");
++  public static final Icon Add = load("/icons/add.png");
++}
index 0000000000000000000000000000000000000000,5ba5366bc878f5b3de19b76424ef1d8446279403..5ba5366bc878f5b3de19b76424ef1d8446279403
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,56d6d91782afea08e344bdbf3f75ed5bb6e01ef1..56d6d91782afea08e344bdbf3f75ed5bb6e01ef1
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,f2a407153a9d98d40e05d58657e191759f12b61f..f2a407153a9d98d40e05d58657e191759f12b61f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,df54db922179da1bbf195e169daeaf86cd9df237..df54db922179da1bbf195e169daeaf86cd9df237
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,a5f682955a2b2e2d35338667d26229691a44a5c3..a5f682955a2b2e2d35338667d26229691a44a5c3
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,f20ae61e8b3706079dd9414a0c30f4f9eeeee058..f20ae61e8b3706079dd9414a0c30f4f9eeeee058
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,d16eb650937f47c14b75af5cc4e9821be8be03f7..d16eb650937f47c14b75af5cc4e9821be8be03f7
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,6753bb85aeb4663753b3422d17d187ddf9f510a6..6753bb85aeb4663753b3422d17d187ddf9f510a6
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,fa22a3c7dfbe2788d979f0822c1ab02a99aaf1a4..fa22a3c7dfbe2788d979f0822c1ab02a99aaf1a4
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,1ce847b3bc35d4f896cb1806f99d3054ad26f621..1ce847b3bc35d4f896cb1806f99d3054ad26f621
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,dce14f8e88c1bfbebc70e9f2337a23f0bc4ad9bf..dce14f8e88c1bfbebc70e9f2337a23f0bc4ad9bf
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,0dd2ea8ec9d90beff14e81ff831e24908fb9500e..0dd2ea8ec9d90beff14e81ff831e24908fb9500e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,9f14a47380b4e1aafaace16f6f48aa2efc0e8dac..9f14a47380b4e1aafaace16f6f48aa2efc0e8dac
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,1c7ec1eeed23fae626d5243bcec457fcb7ad4fda..1c7ec1eeed23fae626d5243bcec457fcb7ad4fda
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,e4800345eaf24da539387a6a6c1e4e78a54b5b80..e4800345eaf24da539387a6a6c1e4e78a54b5b80
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,1187be55461242df5b0dabeafd31ce71f8592abe..1187be55461242df5b0dabeafd31ce71f8592abe
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,2cd5d4aa4598230ef424a516cc6e9d9ee7a567b3..2cd5d4aa4598230ef424a516cc6e9d9ee7a567b3
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,c747fa98d59d941b7ddac9cbfb8863aac5605a17..c747fa98d59d941b7ddac9cbfb8863aac5605a17
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,59d20736ef96e26ca5234930ada5c40ef855bded..59d20736ef96e26ca5234930ada5c40ef855bded
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,b9c2478b8f2f833764e2418fa2d659051dc08887..b9c2478b8f2f833764e2418fa2d659051dc08887
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,1fe2b72179eb3905ee8132c4a88b9e97d5511d0c..1fe2b72179eb3905ee8132c4a88b9e97d5511d0c
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,4760b462a297f11cde0055ccad36f9337a34f1cd..4760b462a297f11cde0055ccad36f9337a34f1cd
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,436f0e1e54fc6b979cf7feaea7c008afd84857d0..436f0e1e54fc6b979cf7feaea7c008afd84857d0
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,65f527353e8529487d5fdd50e3d155c3ed13d9d6..65f527353e8529487d5fdd50e3d155c3ed13d9d6
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,d9170f42e26424b84eeca2d94022f2b553ce6a5e..d9170f42e26424b84eeca2d94022f2b553ce6a5e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8e6b916e291ee92966f19a38fcb91caff36a7b93..8e6b916e291ee92966f19a38fcb91caff36a7b93
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,0d29edb76a627b86e740b8a8d0b6c214f6bf654e..0d29edb76a627b86e740b8a8d0b6c214f6bf654e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8f9a9289a90a60f7625ac715a0dded57dd159db7..8f9a9289a90a60f7625ac715a0dded57dd159db7
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,73393620ee8496c812e58bed91c1a2a6fab3bd53..73393620ee8496c812e58bed91c1a2a6fab3bd53
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,d12392ebe877cf20b821716c674679b51607b69f..d12392ebe877cf20b821716c674679b51607b69f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,547f7db34b4078b54c9b1a084134aff3db3c0ff6..547f7db34b4078b54c9b1a084134aff3db3c0ff6
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,1c7daa73605c774674df2469776e4eae9bd41898..1c7daa73605c774674df2469776e4eae9bd41898
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,6e3840528d6b94b89e8e1266efc5372c9466e4ae..6e3840528d6b94b89e8e1266efc5372c9466e4ae
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,7a2dfa2fc8ff9cb4cc8c98651c96cd0db0c71e61..7a2dfa2fc8ff9cb4cc8c98651c96cd0db0c71e61
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,48ba060e56733791ae241a3553deeb1b23e270ad..48ba060e56733791ae241a3553deeb1b23e270ad
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,49aa622ae4012c1b95fbf8bdabc4c9481ca4c0ba..49aa622ae4012c1b95fbf8bdabc4c9481ca4c0ba
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,befef3218c848e1b3c9f078bd8177647c168380d..befef3218c848e1b3c9f078bd8177647c168380d
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,4f6adabfa658ae453a90e1aac6a577608507d83f..4f6adabfa658ae453a90e1aac6a577608507d83f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,243f88df079168001562cfd3ab8d56a924dfa3cb..243f88df079168001562cfd3ab8d56a924dfa3cb
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8b345b29e99cf383c9d1132571bd38f6e27bb634..8b345b29e99cf383c9d1132571bd38f6e27bb634
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,1f0069cf901ed1df7d16cef9b3a057a7557c8d9c..1f0069cf901ed1df7d16cef9b3a057a7557c8d9c
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,ad7ae31e18d9cee69cea8bc86c3b449005bad777..ad7ae31e18d9cee69cea8bc86c3b449005bad777
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,ef0297d64a65453497270419719d27191129f66e..ef0297d64a65453497270419719d27191129f66e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,cef61eeffd6f0fb7d4e02332960768c15a69992f..cef61eeffd6f0fb7d4e02332960768c15a69992f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,f3f046c0203aa0bf7819d45d74092302e27e4130..f3f046c0203aa0bf7819d45d74092302e27e4130
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8aa4d2c969df865cfc08e3fd40b0e708e6609dd5..8aa4d2c969df865cfc08e3fd40b0e708e6609dd5
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,f69fa5a7961e8a78d951739f135fb58f02b15290..f69fa5a7961e8a78d951739f135fb58f02b15290
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,e0d88ee3987164d8375aa459d45fd2fa2c70aa5f..e0d88ee3987164d8375aa459d45fd2fa2c70aa5f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,b190c699fb84d9b4752c7a57d1d2762af51afc5a..b190c699fb84d9b4752c7a57d1d2762af51afc5a
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,206c07f2ad1ce86a6fa8d453df62c657cd7e6032..206c07f2ad1ce86a6fa8d453df62c657cd7e6032
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,235030d476af50b57a71ce713b520ccc821b45ca..235030d476af50b57a71ce713b520ccc821b45ca
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,c1a9b8c1166cf5b535b3e7d22b5a91b184920c4c..c1a9b8c1166cf5b535b3e7d22b5a91b184920c4c
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,300caf57469af792d79268567e558654c52271d6..300caf57469af792d79268567e558654c52271d6
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,01aea30a00df0e5d19eb1f7b28af82284a3091cc..01aea30a00df0e5d19eb1f7b28af82284a3091cc
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,99c45c97f4760f9e4b6bf02f8894b2aab1b19e6e..99c45c97f4760f9e4b6bf02f8894b2aab1b19e6e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,438f72b3a3966f96c26986906b1537e1025de143..438f72b3a3966f96c26986906b1537e1025de143
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,c01606f7ea507e8db9b8576c200073eedb84b9f6..c01606f7ea507e8db9b8576c200073eedb84b9f6
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,acc826c63ef9fc7342200b3c9b7af5304e0ee1f0..acc826c63ef9fc7342200b3c9b7af5304e0ee1f0
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,fbbd943f32a075e495edea7e9f6968c0e816b45b..fbbd943f32a075e495edea7e9f6968c0e816b45b
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,3193a0caeb07506b751190bdb2c1c01c80fc23ec..3193a0caeb07506b751190bdb2c1c01c80fc23ec
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,1863fbd4f1d1ade73f768391479bbbbbe016df1e..1863fbd4f1d1ade73f768391479bbbbbe016df1e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,b36816b204bed1c7cc4a397bd360230f4ee24a83..b36816b204bed1c7cc4a397bd360230f4ee24a83
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,6da033cd99fb8af303ffe5e5c94d25883bf2b883..6da033cd99fb8af303ffe5e5c94d25883bf2b883
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,c0115247489e70ecfdd71c12509c0cc2c9ee5c3f..c0115247489e70ecfdd71c12509c0cc2c9ee5c3f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,06cd9fa71200e8d1fb3b13f0da27ef90df32529f..06cd9fa71200e8d1fb3b13f0da27ef90df32529f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,f8d696a3c31e03a46a121bc6bf30e9aae47df8ec..f8d696a3c31e03a46a121bc6bf30e9aae47df8ec
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,1bbd57da48dc959789eefe4c2811b13432ea5731..1bbd57da48dc959789eefe4c2811b13432ea5731
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,0a66572cecc5d6186271ed4f83d7d56f509f9bac..0a66572cecc5d6186271ed4f83d7d56f509f9bac
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,549f44e54d8487f2d00dc87aed1dc7712059fb7a..549f44e54d8487f2d00dc87aed1dc7712059fb7a
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,af55919b77b45357673946d0f27270abc83e5055..af55919b77b45357673946d0f27270abc83e5055
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,4026c98d226cb26543f711b7f2781efe7528dce0..4026c98d226cb26543f711b7f2781efe7528dce0
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,5fc144b2ba0706cf570db52dd29fd3d076caf073..5fc144b2ba0706cf570db52dd29fd3d076caf073
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,b2364fc4a3a291375a0cb7867dfb381b8fa0002c..b2364fc4a3a291375a0cb7867dfb381b8fa0002c
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8c996adc5e8b1755bb434e83cbc59bc2199ccb97..8c996adc5e8b1755bb434e83cbc59bc2199ccb97
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,e8a33464106e17875d343e2036284ab199df1fa5..e8a33464106e17875d343e2036284ab199df1fa5
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8e714a3c29ab53747b2c297bea0be550683ab650..8e714a3c29ab53747b2c297bea0be550683ab650
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,da73043d55f68ad4fdf4c835e6ff781baede26d2..da73043d55f68ad4fdf4c835e6ff781baede26d2
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,90867e6f2f4004baf2b50a9f3c0a736c2445174e..90867e6f2f4004baf2b50a9f3c0a736c2445174e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,cf2536a52cbc62574017b6d04e64e8fe7fcf46fc..cf2536a52cbc62574017b6d04e64e8fe7fcf46fc
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,3bfa811876f076a7c23e8d81638a1542f5f26e2f..3bfa811876f076a7c23e8d81638a1542f5f26e2f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,7d02906860d5a430ee9f7965477821d5ac285446..7d02906860d5a430ee9f7965477821d5ac285446
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,a531b49e16187bb4290bcf3c639b6a18f34a4614..a531b49e16187bb4290bcf3c639b6a18f34a4614
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,7d66adb2ce63ba1a1bb0f31098137ad18d305c7a..7d66adb2ce63ba1a1bb0f31098137ad18d305c7a
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,67d731c83ae566d54ccebaa078e04d7268db2de5..67d731c83ae566d54ccebaa078e04d7268db2de5
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,017e5111d1a3ada5256bcfd98f5348de706d1cf2..017e5111d1a3ada5256bcfd98f5348de706d1cf2
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,104be26a84fd02ce7ff2318a14e3a822850179f7..104be26a84fd02ce7ff2318a14e3a822850179f7
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,88bbbe770f723b0ff0170af494c8626dd6e6ed49..88bbbe770f723b0ff0170af494c8626dd6e6ed49
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,cf98278ceb403658d7856bb7bb612a590a766d1c..cf98278ceb403658d7856bb7bb612a590a766d1c
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,3f16f93f52a52554a4f6df1e6d8009b3a79a0b86..3f16f93f52a52554a4f6df1e6d8009b3a79a0b86
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,485aa5dac62fee7bba2798ca4d6bff64554b8404..485aa5dac62fee7bba2798ca4d6bff64554b8404
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8a592406c04c2d18d020960bac9fdc750c8078ef..8a592406c04c2d18d020960bac9fdc750c8078ef
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,0f2f7ca45c82ffccd63e6d25430089343e4ffab9..0f2f7ca45c82ffccd63e6d25430089343e4ffab9
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,418f41b7959d8c82dfe3796a1f46f8dc733a3ae5..418f41b7959d8c82dfe3796a1f46f8dc733a3ae5
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,9a7bf21682f8797bc13b4c98bc0928f675138b99..9a7bf21682f8797bc13b4c98bc0928f675138b99
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,2128e0f102ee4c599bae336ff84104d8c3578efc..2128e0f102ee4c599bae336ff84104d8c3578efc
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,18e86f0b7db62c122993ca3f4ba0ba31b51371b9..18e86f0b7db62c122993ca3f4ba0ba31b51371b9
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,a53151d451fc55bb17ac5e2d3cf09a7576d1df01..a53151d451fc55bb17ac5e2d3cf09a7576d1df01
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,79fbca30c19caadf9d5f6ab870f9110e2040fb1a..79fbca30c19caadf9d5f6ab870f9110e2040fb1a
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,bf92fb3e14245fe3271027565448f0db351caa0f..bf92fb3e14245fe3271027565448f0db351caa0f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,7b450fd86f2ee51137993cfbbbde08dab9146dfa..7b450fd86f2ee51137993cfbbbde08dab9146dfa
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,3a7d37aaf5f2e0527be0c0039bc259c00ba3c910..3a7d37aaf5f2e0527be0c0039bc259c00ba3c910
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,a5dfbbafff8e9fde40e84798c33365354e515bff..a5dfbbafff8e9fde40e84798c33365354e515bff
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,224454ec0dd27214da81a1d5455f08b6bfde23ba..224454ec0dd27214da81a1d5455f08b6bfde23ba
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,0d1ee2077e80b8013bbbb0583238baf1a5aa338b..0d1ee2077e80b8013bbbb0583238baf1a5aa338b
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,752de247500efbf50134a69258466ede09ac4aa6..752de247500efbf50134a69258466ede09ac4aa6
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8269f049f3bf940337a1860581725eaad8e112e8..8269f049f3bf940337a1860581725eaad8e112e8
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,38e9bd3ed65e5cda45d92adafee7dd0e89a99056..38e9bd3ed65e5cda45d92adafee7dd0e89a99056
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,ba0623b7b5755c29b67653130e1c8c673b8044f6..ba0623b7b5755c29b67653130e1c8c673b8044f6
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8e4a4d5e376b30f866b2d7a2671662deefd0cbeb..8e4a4d5e376b30f866b2d7a2671662deefd0cbeb
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,3189a133cabca2eac851fe37bf521c54bcfbcc5f..3189a133cabca2eac851fe37bf521c54bcfbcc5f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,3af9bb6775e6449476506a85ed951048ca196088..3af9bb6775e6449476506a85ed951048ca196088
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,6a754829926e16ecff78787a0f8d1a731d13a59f..6a754829926e16ecff78787a0f8d1a731d13a59f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,d3c142e2f858aeca9cd6657d41921ae48b6a66dc..d3c142e2f858aeca9cd6657d41921ae48b6a66dc
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,e590f9c3d5ba2041079ccac2c03126224d876b85..e590f9c3d5ba2041079ccac2c03126224d876b85
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,527aa819cea33bcf9edc6b901fa5a411db0cc17b..527aa819cea33bcf9edc6b901fa5a411db0cc17b
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,7c03c1c57ed52cb00d8f19380b986c96206582a2..7c03c1c57ed52cb00d8f19380b986c96206582a2
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,cdc501c2492e8a44459e2a8a43566d6af88cce16..cdc501c2492e8a44459e2a8a43566d6af88cce16
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,719ddfbdca8347bbf0d80b23e45e8a68cad048be..719ddfbdca8347bbf0d80b23e45e8a68cad048be
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,6cde1578daf04ba10841fa7e9038deeda220e46f..6cde1578daf04ba10841fa7e9038deeda220e46f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,4d4b3e154160f5b6e9d27e1a195b2f8bae55b5e6..4d4b3e154160f5b6e9d27e1a195b2f8bae55b5e6
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,2ec8adda6e4c11b291ff2be4d7de249f84705455..2ec8adda6e4c11b291ff2be4d7de249f84705455
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,772a53b163128078ab01811a8d6f5a292b5a3037..772a53b163128078ab01811a8d6f5a292b5a3037
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,be1c3e472e5795dab0ddec5b10023d97e7185420..be1c3e472e5795dab0ddec5b10023d97e7185420
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,afd1ca507bf3008eca8b577fb84ec770e0e18cc2..afd1ca507bf3008eca8b577fb84ec770e0e18cc2
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,48de6a961ed550b5293354d009c99be71f1c249c..48de6a961ed550b5293354d009c99be71f1c249c
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,feae35de4423451541435bc72848de24d52ce7db..feae35de4423451541435bc72848de24d52ce7db
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,aaca8bf242a2465e711cef566ffd242774cbb350..aaca8bf242a2465e711cef566ffd242774cbb350
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,79f5a7a10c9cdcb2c9c23b12b41afbacdd078f86..79f5a7a10c9cdcb2c9c23b12b41afbacdd078f86
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,b60345e075aa5a41d7d5f5290c0d83b09750bcb1..b60345e075aa5a41d7d5f5290c0d83b09750bcb1
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,d938b0d9cf23ec80530c0ffa4256777daed70375..d938b0d9cf23ec80530c0ffa4256777daed70375
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,fa1b11dbeec6f988ede8ad2d94cb0e433608b911..fa1b11dbeec6f988ede8ad2d94cb0e433608b911
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,ee9913d523f2f1ab6dd687021bec8389f82083d9..ee9913d523f2f1ab6dd687021bec8389f82083d9
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,52c4e092fcc00f7d2554aeec50ec138181eb6906..52c4e092fcc00f7d2554aeec50ec138181eb6906
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,c82f2d629b6233340d6740079080441c5efed23d..c82f2d629b6233340d6740079080441c5efed23d
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,fa63b65035b9d7b323c629ae6e586089e42a718e..fa63b65035b9d7b323c629ae6e586089e42a718e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8b2c227fe1fa632645fffdd8d0a03ea87e6c4725..8b2c227fe1fa632645fffdd8d0a03ea87e6c4725
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,ee571897337054754a1ec4f1b573fc8b2cb89169..ee571897337054754a1ec4f1b573fc8b2cb89169
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,cccd8a1fda09677337dae4b6eb078a8b4d37eee8..cccd8a1fda09677337dae4b6eb078a8b4d37eee8
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,f323b5912e1c542e90b72bdb57530edb30c286e8..f323b5912e1c542e90b72bdb57530edb30c286e8
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,ddc6aa0a2a53a1e158419d53e7adb65e439df494..ddc6aa0a2a53a1e158419d53e7adb65e439df494
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,344801f70372579c2acd0ef8519ed967de943c85..344801f70372579c2acd0ef8519ed967de943c85
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,d87a8f7bfa6fe40af2a9a6aea564722b8236a25a..d87a8f7bfa6fe40af2a9a6aea564722b8236a25a
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,dbaeabba74ebf9acfa4e535bbdfaa15cbd64d82e..dbaeabba74ebf9acfa4e535bbdfaa15cbd64d82e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,84c998b1890b1d14eac97e4d9e83dc2dd0a3254b..84c998b1890b1d14eac97e4d9e83dc2dd0a3254b
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,d096eff9e9d028bbda415db3f3c207d78ffae7e5..d096eff9e9d028bbda415db3f3c207d78ffae7e5
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,4675ab3c121adccf2315b194113b88914164303e..4675ab3c121adccf2315b194113b88914164303e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,85a68c20599187274ba79ed6e0ec784f38b44fd7..85a68c20599187274ba79ed6e0ec784f38b44fd7
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,b96f1b3778285ec7088bffaa32be8d1b48b7a8d8..b96f1b3778285ec7088bffaa32be8d1b48b7a8d8
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,920edef42a7a856c7fe6975ec64dfdc2ebd4bdcd..920edef42a7a856c7fe6975ec64dfdc2ebd4bdcd
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,6fab6ee4a702b1911062f3eab9b3c7dee05d014e..6fab6ee4a702b1911062f3eab9b3c7dee05d014e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,fb52ceb79f4d2b9d80e52807f5b7ec5223f62ed8..fb52ceb79f4d2b9d80e52807f5b7ec5223f62ed8
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,855e10d1a44af57bfe1f95fc5c8d17a98d4e256a..855e10d1a44af57bfe1f95fc5c8d17a98d4e256a
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,ee15df1b6d93ce3439605d93b9c588ed1c29aa29..ee15df1b6d93ce3439605d93b9c588ed1c29aa29
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,8a2ef02761f0e3afbdf0ba3e934fc9d8894cf37e..8a2ef02761f0e3afbdf0ba3e934fc9d8894cf37e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,75cf5bdb4aad2d71753f04efaba53667054c91dd..75cf5bdb4aad2d71753f04efaba53667054c91dd
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,eeb662af18e6cc7c01fcd6ec0d6e5e3cab656494..eeb662af18e6cc7c01fcd6ec0d6e5e3cab656494
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,72f3c6f68c47b95384692094f7104eb6cf362328..72f3c6f68c47b95384692094f7104eb6cf362328
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,c1ab649b6584a6012cbce7f7135e0797217c7579..c1ab649b6584a6012cbce7f7135e0797217c7579
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,80ee0e6b8cabe430757a26a437d3901f8ae97819..80ee0e6b8cabe430757a26a437d3901f8ae97819
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,f5a9a8f564da2f8f46b7cb3c9eac6400fb71a53e..f5a9a8f564da2f8f46b7cb3c9eac6400fb71a53e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,a225cea325c95f3f0b6385b803a29918794795d8..a225cea325c95f3f0b6385b803a29918794795d8
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,ea9bc951c91e6cd0815e9f39ee93115afeeb9130..ea9bc951c91e6cd0815e9f39ee93115afeeb9130
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,be86e9bf0d4209ae53bd94e2f6e9d7241a9422bc..be86e9bf0d4209ae53bd94e2f6e9d7241a9422bc
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,49f2696dd1344f2ffb2e22ab71467a4a4b200cb6..49f2696dd1344f2ffb2e22ab71467a4a4b200cb6
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,e363bb30ed3103e1d0a53ad95e660778d5ab4947..e363bb30ed3103e1d0a53ad95e660778d5ab4947
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,c1ab649b6584a6012cbce7f7135e0797217c7579..c1ab649b6584a6012cbce7f7135e0797217c7579
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,c09134a31c08836895552d9c56863792c8fa500f..c09134a31c08836895552d9c56863792c8fa500f
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,f5a9a8f564da2f8f46b7cb3c9eac6400fb71a53e..f5a9a8f564da2f8f46b7cb3c9eac6400fb71a53e
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,4fa26cf69a989ada9bf5194e0c20eee0c1df2e47..4fa26cf69a989ada9bf5194e0c20eee0c1df2e47
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,4a8859e729f462644ba9b02afb10909c5253af06..4a8859e729f462644ba9b02afb10909c5253af06
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,1182b7815c067a393b186b5a797e3adef23e23dc..1182b7815c067a393b186b5a797e3adef23e23dc
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,c17e6cdd7479f36a120ed81968ffb8e49688ae84..c17e6cdd7479f36a120ed81968ffb8e49688ae84
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..27a6e362cdede345b83b9cc24ec604c54e69e249
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6efcb97c56a76368e8a5fbe512d16b5513c161ce
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4992191eb9e7a41c43209f38dd5c48f07a83b6f9
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9494f2d0c72e6258751e18225967aefd34acf3a5
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..72461343aa2571fb1b9d136196e3252e5dffee72
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,d3815dd80867d176d81d5b64e5fd47ccec200525..d3815dd80867d176d81d5b64e5fd47ccec200525
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7ca7e03b12cff643a21e1a1ca151416d88116039
new file mode 100755 (executable)
Binary files differ
index 0000000000000000000000000000000000000000,3a9716e4e1fbd57ea4f08f9a7f052a37f1ab44a5..3a9716e4e1fbd57ea4f08f9a7f052a37f1ab44a5
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..dd1a5d9aebf32f2f30a1d482ea8b4c3e62d12959
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,63d1810bc94bd7d45a782fea88d18f454cbc64f3..63d1810bc94bd7d45a782fea88d18f454cbc64f3
mode 000000,100644..100644
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..0656f81eee834a8a97627ab1196c74c0a4de970c
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d595f6b42f56ea159286004ffad65904d60ccbeb
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..218f075d0c18085e528117a39a51c1c94dea3177
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7ef960bcf244fdb7a8e3d9cbb2ff993106633d52
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..99aaa1d20a32ed1c3736056e4294a1eef1898747
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..f10fd560464a494e76fac3b239a6d597d5264933
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a3586004a491e2eb3496625fc4f6dcb3af49aee4
new file mode 100644 (file)
Binary files differ
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..45121805a5bd6d03382d783866c40543a2c8a233
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,76 @@@
++<!--suppress XmlUnboundNsPrefix -->
++<idea-plugin version="2">
++  <id>ru.compscicenter.edide</id>
++  <name>Educational Python IDE</name>
++  <version>1.0</version>
++  <vendor email="support@yourcompany.com" url="http://www.yourcompany.com">YourCompany</vendor>
++
++  <description><![CDATA[
++      Enter short description for your plugin here.<br>
++      <small>most HTML tags may be used</small>
++      ]]></description>
++
++  <change-notes><![CDATA[
++      Add change notes here.<br>
++      <small>most HTML tags may be used</small>
++      ]]>
++  </change-notes>
++
++  <!-- please see http://confluence.jetbrains.net/display/IDEADEV/Build+Number+Ranges for description -->
++  <idea-version since-build="107.105"/>
++
++  <!--depends>com.intellij.modules.python</depends-->
++
++  <!-- please see http://confluence.jetbrains.net/display/IDEADEV/Plugin+Compatibility+with+IntelliJ+Platform+Products
++       on how to target different products -->
++
++  <depends>com.intellij.modules.lang</depends>
++
++  <application-components>
++  </application-components>
++
++  <project-components>
++    <component>
++      <implementation-class>com.jetbrains.python.edu.StudyTaskManager</implementation-class>
++      <interface-class>com.jetbrains.python.edu.StudyTaskManager</interface-class>
++    </component>
++  </project-components>
++
++  <actions>
++    <action id="CheckAction" class="com.jetbrains.python.edu.actions.CheckAction" text="check"
++            description="Runs tests for current tasks" icon="/icons/icon.jpg">
++    </action>
++    <action id="PrevWindowAction" class="com.jetbrains.python.edu.actions.PrevWindowAction" text="PrevWindowAction" description="prev"
++            icon="/icons/prev.png">
++      <add-to-group group-id="MainToolBar" anchor="last"/>
++    </action>
++
++    <action id="NextWindow" class="com.jetbrains.python.edu.actions.NextWindowAction" text="NextWindowAction" description="next"
++            icon="/icons/next.png">
++      <add-to-group group-id="MainToolBar" anchor="last"/>
++    </action>
++    <action id="NextTaskAction" class="com.jetbrains.python.edu.actions.NextTaskAction" text="NextTaskAction" description="Next Task"/>
++    <action id="PreviousTaskAction" class="com.jetbrains.python.edu.actions.PreviousTaskAction" text="PreviousTaskAction"
++            description="Previous Task"/>
++    <action id="RefreshTaskAction" class="com.jetbrains.python.edu.actions.RefreshTaskAction" text="RefreshTaskAction"
++            description="Refresh current task"/>
++    <action id="WatchInputAction" class="com.jetbrains.python.edu.actions.WatchInputAction" text="WatchInputAction"
++            description="watch input"/>
++    <action id="StudyRunAction" class="com.jetbrains.python.edu.actions.StudyRunAction" text="StudyRunAction" description="run your code"/>
++    <action id="ShowHintAction" class="com.jetbrains.python.edu.actions.ShowHintAction" text="Show hint"
++            description="show hint" icon="/icons/showHint.png">
++      <add-to-group group-id="MainToolBar" anchor="last"/>
++    </action>
++  </actions>
++
++  <extensions defaultExtensionNs="com.intellij">
++    <toolWindow id="StudyToolWindow" anchor="right" factoryClass="com.jetbrains.python.edu.ui.StudyToolWindowFactory"
++        icon="/icons/showHint.png" conditionClass="com.jetbrains.python.edu.ui.StudyCondition"/>
++    <fileEditorProvider implementation="com.jetbrains.python.edu.editor.StudyFileEditorProvider"/>
++    <directoryProjectGenerator implementation="com.jetbrains.python.edu.StudyDirectoryProjectGenerator"/>
++    <treeStructureProvider implementation="com.jetbrains.python.edu.projectView.StudyTreeStructureProvider"/>
++    <highlightErrorFilter implementation="com.jetbrains.python.edu.StudyHighlightErrorFilter"/>
++    <applicationService serviceInterface="com.intellij.openapi.fileEditor.impl.EditorEmptyTextPainter"
++        serviceImplementation="com.jetbrains.python.edu.StudyInstructionPainter" overrides="true"/>
++  </extensions>
++</idea-plugin>
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..38886a04fd691275a0b68fa8a3db77c24d0cc276
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,377 @@@
++package com.jetbrains.python.edu;
++
++import com.google.gson.FieldNamingPolicy;
++import com.google.gson.Gson;
++import com.google.gson.GsonBuilder;
++import com.google.gson.JsonParser;
++import com.intellij.facet.ui.FacetEditorValidator;
++import com.intellij.facet.ui.FacetValidatorsManager;
++import com.intellij.facet.ui.ValidationResult;
++import com.intellij.lang.javascript.boilerplate.GithubDownloadUtil;
++import com.intellij.openapi.application.PathManager;
++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.vfs.VirtualFile;
++import com.intellij.openapi.vfs.VirtualFileManager;
++import com.intellij.platform.DirectoryProjectGenerator;
++import com.intellij.platform.templates.github.GeneratorException;
++import com.intellij.platform.templates.github.ZipUtil;
++import com.jetbrains.python.newProject.PythonProjectGenerator;
++import icons.StudyIcons;
++import org.jetbrains.annotations.Nls;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++import com.jetbrains.python.edu.course.Course;
++import com.jetbrains.python.edu.course.CourseInfo;
++import com.jetbrains.python.edu.ui.StudyNewProjectPanel;
++
++import javax.swing.*;
++import java.io.*;
++import java.util.HashMap;
++import java.util.Map;
++import java.util.regex.Matcher;
++import java.util.regex.Pattern;
++
++
++public class StudyDirectoryProjectGenerator extends PythonProjectGenerator implements DirectoryProjectGenerator {
++  private static final Logger LOG = Logger.getInstance(StudyDirectoryProjectGenerator.class.getName());
++  private static final String REPO_URL = "https://github.com/medvector/initial-python-course/archive/master.zip";
++  private static final String USER_NAME = "medvector";
++  private static final String COURSE_META_FILE = "course.json";
++  private static final String COURSE_NAME_ATTRIBUTE = "name";
++  private static final Pattern CACHE_PATTERN = Pattern.compile("(name=(.*)) (path=(.*course.json)) (author=(.*)) (description=(.*))");
++  private static final String REPOSITORY_NAME = "initial-python-course";
++  public static final String AUTHOR_ATTRIBUTE = "author";
++  private final File myCoursesDir = new File(PathManager.getLibPath(), "courses");
++  private static final String CACHE_NAME = "courseNames.txt";
++  private Map<CourseInfo, File> myCourses = new HashMap<CourseInfo, File>();
++  private File mySelectedCourseFile;
++  private Project myProject;
++  public ValidationResult myValidationResult = new ValidationResult("selected course is not valid");
++
++  @Nls
++  @NotNull
++  @Override
++  public String getName() {
++    return "Study project";
++  }
++
++
++  public void setCourses(Map<CourseInfo, File> courses) {
++    myCourses = courses;
++  }
++
++  /**
++   * Finds selected course in courses by name.
++   *
++   * @param courseName name of selected course
++   */
++  public void setSelectedCourse(@NotNull CourseInfo courseName) {
++    File courseFile = myCourses.get(courseName);
++    if (courseFile == null) {
++      LOG.error("invalid course in list");
++    }
++    mySelectedCourseFile = courseFile;
++  }
++
++  /**
++   * Adds course to courses specified in params
++   *
++   * @param courseDir must be directory containing course file
++   * @return added course name or null if course is invalid
++   */
++  @Nullable
++  private CourseInfo addCourse(Map<CourseInfo, File> courses, File courseDir) {
++    if (courseDir.isDirectory()) {
++      File[] courseFiles = courseDir.listFiles(new FilenameFilter() {
++        @Override
++        public boolean accept(File dir, String name) {
++          return name.equals(COURSE_META_FILE);
++        }
++      });
++      if (courseFiles.length != 1) {
++        LOG.info("User tried to add course with more than one or without course files");
++        return null;
++      }
++      File courseFile = courseFiles[0];
++      CourseInfo courseInfo = getCourseInfo(courseFile);
++      if (courseInfo != null) {
++        courses.put(courseInfo, courseFile);
++      }
++      return courseInfo;
++    }
++    return null;
++  }
++
++
++  /**
++   * Adds course from zip archive to courses
++   *
++   * @return added course name or null if course is invalid
++   */
++  @Nullable
++  public CourseInfo addLocalCourse(String zipFilePath) {
++    File file = new File(zipFilePath);
++    try {
++      String fileName = file.getName();
++      String unzippedName = fileName.substring(0, fileName.indexOf("."));
++      File courseDir = new File(myCoursesDir, unzippedName);
++      ZipUtil.unzip(null, courseDir, file, null, null, true);
++      CourseInfo courseName = addCourse(myCourses, courseDir);
++      flushCache();
++      return courseName;
++    }
++    catch (IOException e) {
++      LOG.error("Failed to unzip course archive");
++      LOG.error(e);
++    }
++    return null;
++  }
++
++  @Nullable
++  @Override
++  public Object showGenerationSettings(VirtualFile baseDir) throws ProcessCanceledException {
++    return null;
++  }
++
++  @Nullable
++  @Override
++  public Icon getLogo() {
++    return StudyIcons.Playground;
++  }
++
++
++  @Override
++  public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir,
++                              @Nullable Object settings, @NotNull Module module) {
++
++    myProject = project;
++    Reader reader = null;
++    try {
++      reader = new InputStreamReader(new FileInputStream(mySelectedCourseFile));
++      Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
++      Course course = gson.fromJson(reader, Course.class);
++      course.init(false);
++      course.create(baseDir, new File(mySelectedCourseFile.getParent()));
++      course.setResourcePath(mySelectedCourseFile.getAbsolutePath());
++      VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
++      StudyTaskManager.getInstance(project).setCourse(course);
++    }
++    catch (FileNotFoundException e) {
++      e.printStackTrace();
++    }
++    finally {
++      StudyUtils.closeSilently(reader);
++    }
++  }
++
++  /**
++   * Downloads courses from {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#REPO_URL}
++   * and unzips them into {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#myCoursesDir}
++   */
++
++  public void downloadAndUnzip(boolean needProgressBar) {
++    File outputFile = new File(PathManager.getLibPath(), "courses.zip");
++    try {
++      if (!needProgressBar) {
++        GithubDownloadUtil.downloadAtomically(null, REPO_URL,
++                                              outputFile, USER_NAME, REPOSITORY_NAME);
++      }
++      else {
++        GithubDownloadUtil.downloadContentToFileWithProgressSynchronously(myProject, REPO_URL, "downloading courses", outputFile, USER_NAME,
++                                                                          REPOSITORY_NAME, false);
++      }
++      if (outputFile.exists()) {
++        ZipUtil.unzip(null, myCoursesDir, outputFile, null, null, true);
++        if (!outputFile.delete()) {
++          LOG.error("Failed to delete", outputFile.getName());
++        }
++        File[] files = myCoursesDir.listFiles();
++        if (files != null) {
++          for (File file : files) {
++            String fileName = file.getName();
++            if (StudyUtils.isZip(fileName)) {
++              ZipUtil.unzip(null, new File(myCoursesDir, fileName.substring(0, fileName.indexOf("."))), file, null, null, true);
++              if (!file.delete()) {
++                LOG.error("Failed to delete", fileName);
++              }
++            }
++          }
++        }
++      }
++    }
++    catch (IOException e) {
++      e.printStackTrace();
++    }
++    catch (GeneratorException e) {
++      e.printStackTrace();
++    }
++  }
++
++  public Map<CourseInfo, File> getLoadedCourses() {
++    return myCourses;
++  }
++
++  /**
++   * Parses courses located in {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#myCoursesDir}
++   * to {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#myCourses}
++   *
++   * @return map with course names and course files location
++   */
++  public Map<CourseInfo, File> loadCourses() {
++    Map<CourseInfo, File> courses = new HashMap<CourseInfo, File>();
++    if (myCoursesDir.exists()) {
++      File[] courseDirs = myCoursesDir.listFiles(new FileFilter() {
++        @Override
++        public boolean accept(File pathname) {
++          return pathname.isDirectory();
++        }
++      });
++      for (File courseDir : courseDirs) {
++        addCourse(courses, courseDir);
++      }
++    }
++    return courses;
++  }
++
++  /**
++   * Parses course json meta file and finds course name
++   *
++   * @return information about course or null if course file is invalid
++   */
++  @Nullable
++  private CourseInfo getCourseInfo(File courseFile) {
++    CourseInfo courseInfo = null;
++    BufferedReader reader = null;
++    try {
++      if (courseFile.getName().equals(COURSE_META_FILE)) {
++        reader = new BufferedReader(new InputStreamReader(new FileInputStream(courseFile)));
++        com.google.gson.stream.JsonReader r = new com.google.gson.stream.JsonReader(reader);
++        JsonParser parser = new JsonParser();
++        com.google.gson.JsonElement el = parser.parse(r);
++        String courseName = el.getAsJsonObject().get(COURSE_NAME_ATTRIBUTE).getAsString();
++        String courseAuthor = el.getAsJsonObject().get(AUTHOR_ATTRIBUTE).getAsString();
++        String courseDescription = el.getAsJsonObject().get("description").getAsString();
++        courseInfo = new CourseInfo(courseName, courseAuthor, courseDescription);
++      }
++    }
++    catch (FileNotFoundException e) {
++      e.printStackTrace();
++    }
++    finally {
++      StudyUtils.closeSilently(reader);
++    }
++    return courseInfo;
++  }
++
++  @NotNull
++  @Override
++  public ValidationResult validate(@NotNull String s) {
++    return myValidationResult;
++  }
++
++  public void setValidationResult(ValidationResult validationResult) {
++    myValidationResult = validationResult;
++  }
++
++  /**
++   * @return courses from memory or from cash file or parses course directory
++   */
++  public Map<CourseInfo, File> getCourses() {
++    if (!myCourses.isEmpty()) {
++      return myCourses;
++    }
++    if (myCoursesDir.exists()) {
++      File cacheFile = new File(myCoursesDir, CACHE_NAME);
++      if (cacheFile.exists()) {
++        myCourses = getCoursesFromCache(cacheFile);
++        if (!myCourses.isEmpty()) {
++          return myCourses;
++        }
++      }
++      myCourses = loadCourses();
++      if (!myCourses.isEmpty()) {
++        return myCourses;
++      }
++    }
++    downloadAndUnzip(false);
++    myCourses = loadCourses();
++    flushCache();
++    return myCourses;
++  }
++
++  /**
++   * Writes courses to cash file {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#CACHE_NAME}
++   */
++  public void flushCache() {
++    File cashFile = new File(myCoursesDir, CACHE_NAME);
++    PrintWriter writer = null;
++    try {
++      writer = new PrintWriter(cashFile);
++      for (Map.Entry<CourseInfo, File> course : myCourses.entrySet()) {
++        CourseInfo courseInfo = course.getKey();
++        String line = String
++          .format("name=%s path=%s author=%s description=%s", courseInfo.getName(), course.getValue(), courseInfo.getAuthor(),
++                  courseInfo.getDescription());
++        writer.println(line);
++      }
++    }
++    catch (FileNotFoundException e) {
++      e.printStackTrace();
++    }
++    finally {
++      StudyUtils.closeSilently(writer);
++    }
++  }
++
++  /**
++   * Loads courses from {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#CACHE_NAME} file
++   *
++   * @return map of course names and course files
++   */
++  private Map<CourseInfo, File> getCoursesFromCache(File cashFile) {
++    Map<CourseInfo, File> coursesFromCash = new HashMap<CourseInfo, File>();
++    try {
++      BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(cashFile)));
++      String line;
++
++      while ((line = reader.readLine()) != null) {
++        Matcher matcher = CACHE_PATTERN.matcher(line);
++        if (matcher.matches()) {
++          String courseName = matcher.group(2);
++          File file = new File(matcher.group(4));
++          String author = matcher.group(6);
++          String description = matcher.group(8);
++          CourseInfo courseInfo = new CourseInfo(courseName, author, description);
++          if (file.exists()) {
++            coursesFromCash.put(courseInfo, file);
++          }
++        }
++      }
++    }
++    catch (FileNotFoundException e) {
++      e.printStackTrace();
++    }
++    catch (IOException e) {
++      e.printStackTrace();
++    }
++    return coursesFromCash;
++  }
++
++  @Nullable
++  @Override
++  public JPanel extendBasePanel() throws ProcessCanceledException {
++    StudyNewProjectPanel settingsPanel = new StudyNewProjectPanel(this);
++    settingsPanel.registerValidators(new FacetValidatorsManager() {
++      public void registerValidator(FacetEditorValidator validator, JComponent... componentsToWatch) {
++        throw new UnsupportedOperationException();
++      }
++      public void validate() {
++        fireStateChanged();
++      }
++    });
++    return settingsPanel.getContentPanel();
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..0d26168a3ebcaad1c4de791da6106c7f712013ed
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,66 @@@
++package com.jetbrains.python.edu;
++
++
++import com.intellij.openapi.editor.Document;
++import com.intellij.openapi.editor.LogicalPosition;
++import com.intellij.openapi.editor.event.DocumentAdapter;
++import com.intellij.openapi.editor.event.DocumentEvent;
++import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
++import com.jetbrains.python.edu.course.TaskFile;
++import com.jetbrains.python.edu.course.TaskWindow;
++
++/**
++ * author: liana
++ * data: 7/16/14.
++ * Listens changes in study files and updates
++ * coordinates of all the windows in current task file
++ */
++public class StudyDocumentListener extends DocumentAdapter {
++  private final TaskFile myTaskFile;
++  private int oldLine;
++  private int oldLineStartOffset;
++  private TaskWindow myTaskWindow;
++
++  public StudyDocumentListener(TaskFile taskFile) {
++    myTaskFile = taskFile;
++  }
++
++
++  //remembering old end before document change because of problems
++  // with fragments containing "\n"
++  @Override
++  public void beforeDocumentChange(DocumentEvent e) {
++    int offset = e.getOffset();
++    int oldEnd = offset + e.getOldLength();
++    Document document = e.getDocument();
++    oldLine = document.getLineNumber(oldEnd);
++    oldLineStartOffset = document.getLineStartOffset(oldLine);
++    int line = document.getLineNumber(offset);
++    int offsetInLine = offset - document.getLineStartOffset(line);
++    LogicalPosition pos = new LogicalPosition(line, offsetInLine);
++    myTaskWindow = myTaskFile.getTaskWindow(document, pos);
++
++  }
++
++  @Override
++  public void documentChanged(DocumentEvent e) {
++    if (e instanceof DocumentEventImpl) {
++      DocumentEventImpl event = (DocumentEventImpl)e;
++      Document document = e.getDocument();
++      int offset = e.getOffset();
++      int change = event.getNewLength() - event.getOldLength();
++      if (myTaskWindow != null) {
++        int newLength = myTaskWindow.getLength() + change;
++        myTaskWindow.setLength(newLength);
++      }
++      int newEnd = offset + event.getNewLength();
++      int newLine = document.getLineNumber(newEnd);
++      int lineChange = newLine - oldLine;
++      myTaskFile.incrementLines(oldLine + 1, lineChange);
++      int newEndOffsetInLine = offset + e.getNewLength() - document.getLineStartOffset(newLine);
++      int oldEndOffsetInLine = offset + e.getOldLength() - oldLineStartOffset;
++      myTaskFile.updateLine(lineChange, oldLine, newEndOffsetInLine, oldEndOffsetInLine);
++      System.out.println();
++    }
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..92c1a39c12ce9112297e3835c7ab512dea8ac71e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,102 @@@
++package com.jetbrains.python.edu;
++
++
++import com.intellij.openapi.application.ApplicationManager;
++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.vfs.VirtualFile;
++import org.jetbrains.annotations.NotNull;
++import com.jetbrains.python.edu.course.StudyStatus;
++import com.jetbrains.python.edu.course.TaskFile;
++import com.jetbrains.python.edu.course.TaskWindow;
++import com.jetbrains.python.edu.editor.StudyEditor;
++
++import java.awt.*;
++
++/**
++ * User: lia
++ */
++
++class StudyEditorFactoryListener implements EditorFactoryListener {
++
++  /**
++   * draws selected task window if there is one located in mouse position
++   */
++  private class WindowSelectionListener extends EditorMouseAdapter {
++    private final TaskFile myTaskFile;
++
++    WindowSelectionListener(TaskFile taskFile) {
++      myTaskFile = taskFile;
++    }
++
++    @Override
++    public void mouseClicked(EditorMouseEvent e) {
++      Editor editor = e.getEditor();
++      Point point = e.getMouseEvent().getPoint();
++      LogicalPosition pos = editor.xyToLogicalPosition(point);
++      TaskWindow taskWindow = myTaskFile.getTaskWindow(editor.getDocument(), pos);
++      if (taskWindow != null) {
++        myTaskFile.setSelectedTaskWindow(taskWindow);
++        taskWindow.draw(editor, taskWindow.getStatus() != StudyStatus.Solved, true);
++      }
++      else {
++        myTaskFile.drawAllWindows(editor);
++      }
++    }
++  }
++
++  @Override
++  public void editorCreated(@NotNull final EditorFactoryEvent event) {
++    final Editor editor = event.getEditor();
++
++    final Project project = editor.getProject();
++    if (project == null) {
++      return;
++    }
++    ApplicationManager.getApplication().invokeLater(
++      new Runnable() {
++        @Override
++        public void run() {
++          ApplicationManager.getApplication().runWriteAction(new Runnable() {
++            @Override
++            public void run() {
++              Document document = editor.getDocument();
++              VirtualFile openedFile = FileDocumentManager.getInstance().getFile(document);
++              if (openedFile != null) {
++                StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
++                TaskFile taskFile = taskManager.getTaskFile(openedFile);
++                if (taskFile != null) {
++                  editor.addEditorMouseListener(new WindowSelectionListener(taskFile));
++                  StudyDocumentListener listener = new StudyDocumentListener(taskFile);
++                  StudyEditor.addDocumentListener(document, listener);
++                  document.addDocumentListener(listener);
++                  taskFile.drawAllWindows(editor);
++                }
++              }
++            }
++          });
++        }
++      }
++    );
++  }
++
++  @Override
++  public void editorReleased(@NotNull EditorFactoryEvent event) {
++    Editor editor = event.getEditor();
++    Document document = editor.getDocument();
++    StudyDocumentListener listener = StudyEditor.getListener(document);
++    if (listener != null) {
++      document.removeDocumentListener(listener);
++      StudyEditor.removeListener(document);
++    }
++    editor.getMarkupModel().removeAllHighlighters();
++    editor.getSelectionModel().removeSelection();
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1377128ed4172b44acb4b5c3de9a23d648dfa349
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,23 @@@
++package com.jetbrains.python.edu;
++
++import com.intellij.codeInsight.highlighting.HighlightErrorFilter;
++import com.intellij.openapi.project.Project;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.intellij.psi.PsiErrorElement;
++import org.jetbrains.annotations.NotNull;
++import com.jetbrains.python.edu.course.TaskFile;
++
++/**
++ * author: liana
++ * data: 7/23/14.
++ */
++public class StudyHighlightErrorFilter extends HighlightErrorFilter {
++  @Override
++  public boolean shouldHighlightErrorElement(@NotNull final PsiErrorElement element) {
++    VirtualFile file = element.getContainingFile().getVirtualFile();
++    Project project = element.getProject();
++    StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
++    TaskFile taskFile = taskManager.getTaskFile(file);
++    return taskFile == null;
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..7e8e33fb65d4b2646017e42f368bc8d2a81a17a1
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,46 @@@
++package com.jetbrains.python.edu;
++
++import com.intellij.openapi.fileEditor.impl.EditorEmptyTextPainter;
++import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
++import com.intellij.openapi.util.Couple;
++import com.intellij.ui.Gray;
++import com.intellij.ui.JBColor;
++import com.intellij.util.PairFunction;
++import com.intellij.util.ui.GraphicsUtil;
++import com.intellij.util.ui.UIUtil;
++import com.jetbrains.python.edu.ui.StudyCondition;
++
++import java.awt.*;
++
++/**
++ * author: liana
++ * data: 7/29/14.
++ */
++public class StudyInstructionPainter extends EditorEmptyTextPainter {
++  @Override
++  public void paintEmptyText(final EditorsSplitters splitters, Graphics g) {
++    if (!StudyCondition.VALUE) {
++      super.paintEmptyText(splitters, g);
++      return;
++    }
++    boolean isDarkBackground = UIUtil.isUnderDarcula();
++    UIUtil.applyRenderingHints(g);
++    GraphicsUtil.setupAntialiasing(g, true, false);
++    g.setColor(new JBColor(isDarkBackground ? Gray._230 : Gray._80, Gray._160));
++    g.setFont(UIUtil.getLabelFont().deriveFont(isDarkBackground ? 24f : 20f));
++
++    UIUtil.TextPainter painter = new UIUtil.TextPainter().withLineSpacing(1.5f);
++    painter.withShadow(true, new JBColor(Gray._200.withAlpha(100), Gray._0.withAlpha(255)));
++
++    painter.appendLine("Welcome to PyCharm Educational Edition").underlined(new JBColor(Gray._150, Gray._180));
++    painter.appendLine("Learn Python programming in your favorite IDE").smaller();
++    painter.appendLine("Navigate between windows with alt < >").smaller().withBullet();
++    painter.draw(g, new PairFunction<Integer, Integer, Couple<Integer>>() {
++      @Override
++      public Couple<Integer> fun(Integer width, Integer height) {
++        Dimension s = splitters.getSize();
++        return Couple.of((s.width - width) / 2, (s.height - height) / 2);
++      }
++    });
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..38f1c2ff2cdeaa26c3c92a67e2bbe1c6f60af427
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,5 @@@
++package com.jetbrains.python.edu;
++
++public interface StudyResourceManger {
++  String USER_TESTER = "user_tester.py";
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d3e0f33ca56273bd0d0703e96a1e623765e5f9b2
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,283 @@@
++package com.jetbrains.python.edu;
++
++import com.intellij.ide.ui.UISettings;
++import com.intellij.openapi.actionSystem.*;
++import com.intellij.openapi.actionSystem.ex.AnActionListener;
++import com.intellij.openapi.application.ApplicationManager;
++import com.intellij.openapi.components.*;
++import com.intellij.openapi.editor.EditorFactory;
++import com.intellij.openapi.keymap.Keymap;
++import com.intellij.openapi.keymap.KeymapManager;
++import com.intellij.openapi.project.DumbAware;
++import com.intellij.openapi.project.DumbAwareRunnable;
++import com.intellij.openapi.project.Project;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.intellij.openapi.vfs.VirtualFileAdapter;
++import com.intellij.openapi.vfs.VirtualFileEvent;
++import com.intellij.openapi.vfs.VirtualFileManager;
++import com.intellij.openapi.wm.ToolWindow;
++import com.intellij.openapi.wm.ToolWindowAnchor;
++import com.intellij.openapi.wm.ToolWindowManager;
++import com.intellij.util.xmlb.XmlSerializer;
++import icons.StudyIcons;
++import org.jdom.Element;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++import com.jetbrains.python.edu.actions.NextWindowAction;
++import com.jetbrains.python.edu.actions.PrevWindowAction;
++import com.jetbrains.python.edu.actions.ShowHintAction;
++import com.jetbrains.python.edu.course.Course;
++import com.jetbrains.python.edu.course.Lesson;
++import com.jetbrains.python.edu.course.Task;
++import com.jetbrains.python.edu.course.TaskFile;
++import com.jetbrains.python.edu.ui.StudyCondition;
++import com.jetbrains.python.edu.ui.StudyToolWindowFactory;
++
++import javax.swing.*;
++import java.lang.reflect.Method;
++import java.util.HashMap;
++import java.util.List;
++import java.util.Map;
++
++/**
++ * Implementation of class which contains all the information
++ * about study in context of current project
++ */
++
++@State(
++  name = "StudySettings",
++  storages = {
++    @Storage(
++      id = "others",
++      file = "$PROJECT_CONFIG_DIR$/study_project.xml",
++      scheme = StorageScheme.DIRECTORY_BASED
++    )}
++)
++public class StudyTaskManager implements ProjectComponent, PersistentStateComponent<Element>, DumbAware {
++  public static final String COURSE_ELEMENT = "courseElement";
++  private static Map<String, StudyTaskManager> myTaskManagers = new HashMap<String, StudyTaskManager>();
++  private static Map<String, String> myDeletedShortcuts = new HashMap<String, String>();
++  private final Project myProject;
++  private Course myCourse;
++  private FileCreatedListener myListener;
++
++
++  public void setCourse(Course course) {
++    myCourse = course;
++  }
++
++  private StudyTaskManager(@NotNull final Project project) {
++    myTaskManagers.put(project.getBasePath(), this);
++    myProject = project;
++  }
++
++
++  @Nullable
++  public Course getCourse() {
++    return myCourse;
++  }
++
++  @Nullable
++  @Override
++  public Element getState() {
++    Element el = new Element("taskManager");
++    if (myCourse != null) {
++      Element courseElement = new Element(COURSE_ELEMENT);
++      XmlSerializer.serializeInto(myCourse, courseElement);
++      el.addContent(courseElement);
++    }
++    return el;
++  }
++
++  @Override
++  public void loadState(Element el) {
++    myCourse = XmlSerializer.deserialize(el.getChild(COURSE_ELEMENT), Course.class);
++    if (myCourse != null) {
++      myCourse.init(true);
++    }
++  }
++
++  @Override
++  public void projectOpened() {
++    ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
++      @Override
++      public void run() {
++        ApplicationManager.getApplication().runWriteAction(new DumbAwareRunnable() {
++          @Override
++          public void run() {
++            if (myCourse != null) {
++              UISettings.getInstance().HIDE_TOOL_STRIPES = false;
++              UISettings.getInstance().fireUISettingsChanged();
++              ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
++              String toolWindowId = StudyToolWindowFactory.STUDY_TOOL_WINDOW;
++              //TODO:decide smth with tool window position
++              try {
++                Method method = toolWindowManager.getClass().getDeclaredMethod("registerToolWindow", String.class,
++                                                                               JComponent.class,
++                                                                               ToolWindowAnchor.class,
++                                                                               boolean.class, boolean.class, boolean.class);
++                method.setAccessible(true);
++                method.invoke(toolWindowManager, toolWindowId, null, ToolWindowAnchor.LEFT, true, true, true);
++              }
++              catch (Exception e) {
++                toolWindowManager
++                  .registerToolWindow(toolWindowId, true, ToolWindowAnchor.RIGHT, myProject, true);
++              }
++
++              final ToolWindow studyToolWindow = toolWindowManager.getToolWindow(toolWindowId);
++              if (studyToolWindow != null) {
++                StudyUtils.updateStudyToolWindow(myProject);
++                studyToolWindow.setIcon(StudyIcons.ShortcutReminder);
++                studyToolWindow.show(null);
++              }
++              addShortcut(NextWindowAction.SHORTCUT, NextWindowAction.ACTION_ID);
++              addShortcut(PrevWindowAction.SHORTCUT, PrevWindowAction.ACTION_ID);
++              addShortcut(ShowHintAction.SHORTCUT, ShowHintAction.ACTION_ID);
++              addShortcut(NextWindowAction.SHORTCUT2, NextWindowAction.ACTION_ID);
++            }
++          }
++        });
++      }
++    });
++  }
++
++
++  private void addShortcut(@NotNull final String shortcutString, @NotNull final String actionIdString) {
++    Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
++    Shortcut studyActionShortcut = new KeyboardShortcut(KeyStroke.getKeyStroke(shortcutString), null);
++    String[] actionsIds = keymap.getActionIds(studyActionShortcut);
++    for (String actionId : actionsIds) {
++      myDeletedShortcuts.put(actionId, shortcutString);
++      keymap.removeShortcut(actionId, studyActionShortcut);
++    }
++    keymap.addShortcut(actionIdString, studyActionShortcut);
++  }
++
++  @Override
++  public void projectClosed() {
++    StudyCondition.VALUE = false;
++    if (myCourse != null) {
++      ToolWindowManager.getInstance(myProject).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW).getContentManager()
++        .removeAllContents(false);
++      if (!myDeletedShortcuts.isEmpty()) {
++        for (Map.Entry<String, String> shortcut : myDeletedShortcuts.entrySet()) {
++          Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
++          Shortcut actionShortcut = new KeyboardShortcut(KeyStroke.getKeyStroke(shortcut.getValue()), null);
++          keymap.addShortcut(shortcut.getKey(), actionShortcut);
++        }
++      }
++    }
++  }
++
++  @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 FileCreatedListener();
++            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() {
++  }
++
++  @NotNull
++  @Override
++  public String getComponentName() {
++    return "StudyTaskManager";
++  }
++
++  public static StudyTaskManager getInstance(@NotNull final Project project) {
++    StudyTaskManager item = myTaskManagers.get(project.getBasePath());
++    return item != null ? item : new StudyTaskManager(project);
++  }
++
++
++  @Nullable
++  public TaskFile getTaskFile(@NotNull final VirtualFile file) {
++    if (myCourse == null) {
++      return null;
++    }
++    VirtualFile taskDir = file.getParent();
++    if (taskDir != null) {
++      String taskDirName = taskDir.getName();
++      if (taskDirName.contains(Task.TASK_DIR)) {
++        VirtualFile lessonDir = taskDir.getParent();
++        if (lessonDir != null) {
++          String lessonDirName = lessonDir.getName();
++          int lessonIndex = StudyUtils.getIndex(lessonDirName, Lesson.LESSON_DIR);
++          List<Lesson> lessons = myCourse.getLessons();
++          if (!StudyUtils.indexIsValid(lessonIndex, lessons)) {
++            return null;
++          }
++          Lesson lesson = lessons.get(lessonIndex);
++          int taskIndex = StudyUtils.getIndex(taskDirName, Task.TASK_DIR);
++          List<Task> tasks = lesson.getTaskList();
++          if (!StudyUtils.indexIsValid(taskIndex, tasks)) {
++            return null;
++          }
++          Task task = tasks.get(taskIndex);
++          return task.getFile(file.getName());
++        }
++      }
++    }
++    return null;
++  }
++
++  class FileCreatedListener extends VirtualFileAdapter {
++    @Override
++    public void fileCreated(@NotNull VirtualFileEvent event) {
++      VirtualFile createdFile = event.getFile();
++      VirtualFile taskDir = createdFile.getParent();
++      String taskLogicalName = Task.TASK_DIR;
++      if (taskDir != null && taskDir.getName().contains(taskLogicalName)) {
++        int taskIndex = StudyUtils.getIndex(taskDir.getName(), taskLogicalName);
++        VirtualFile lessonDir = taskDir.getParent();
++        String lessonLogicalName = Lesson.LESSON_DIR;
++        if (lessonDir != null && lessonDir.getName().contains(lessonLogicalName)) {
++          int lessonIndex = StudyUtils.getIndex(lessonDir.getName(), lessonLogicalName);
++          if (myCourse != null) {
++            List<Lesson> lessons = myCourse.getLessons();
++            if (StudyUtils.indexIsValid(lessonIndex, lessons)) {
++              Lesson lesson = lessons.get(lessonIndex);
++              List<Task> tasks = lesson.getTaskList();
++              if (StudyUtils.indexIsValid(taskIndex, tasks)) {
++                Task task = tasks.get(taskIndex);
++                TaskFile taskFile = new TaskFile();
++                taskFile.init(task, false);
++                taskFile.setUserCreated(true);
++                task.getTaskFiles().put(createdFile.getName(), taskFile);
++              }
++            }
++          }
++        }
++      }
++    }
++  }
++
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..cc4cd810993327a594430604c390f808849fd480
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,148 @@@
++package com.jetbrains.python.edu;
++
++import com.intellij.ide.SaveAndSyncHandlerImpl;
++import com.intellij.openapi.actionSystem.AnActionEvent;
++import com.intellij.openapi.actionSystem.Presentation;
++import com.intellij.openapi.editor.Document;
++import com.intellij.openapi.fileEditor.FileDocumentManager;
++import com.intellij.openapi.fileEditor.FileEditor;
++import com.intellij.openapi.fileEditor.FileEditorManager;
++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.openapi.wm.ToolWindowManager;
++import com.intellij.util.ui.UIUtil;
++import org.jetbrains.annotations.NotNull;
++import com.jetbrains.python.edu.course.TaskFile;
++import com.jetbrains.python.edu.course.TaskWindow;
++import com.jetbrains.python.edu.editor.StudyEditor;
++import com.jetbrains.python.edu.ui.StudyToolWindowFactory;
++
++import java.io.*;
++import java.util.Collection;
++
++/**
++ * author: liana
++ * data: 7/15/14.
++ */
++public class StudyUtils {
++  public static void closeSilently(Closeable stream) {
++    if (stream != null) {
++      try {
++        stream.close();
++      }
++      catch (IOException e) {
++        // close silently
++      }
++    }
++  }
++
++  public static boolean isZip(String fileName) {
++    return fileName.contains(".zip");
++  }
++
++  public static <T> T getFirst(Iterable<T> container) {
++    return container.iterator().next();
++  }
++
++  public static boolean indexIsValid(int index, Collection collection) {
++    int size = collection.size();
++    return index >= 0 && index < size;
++  }
++
++  public static String getFileText(String parentDir, String fileName, boolean wrapHTML) {
++
++    File inputFile = parentDir !=null ? new File(parentDir, fileName) : new File(fileName);
++    StringBuilder taskText = new StringBuilder();
++    BufferedReader reader = null;
++    try {
++      reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile)));
++      String line;
++      while ((line = reader.readLine()) != null) {
++        taskText.append(line).append("\n");
++        if (wrapHTML) {
++          taskText.append("<br>");
++        }
++      }
++      return wrapHTML ? UIUtil.toHtml(taskText.toString()) : taskText.toString();
++    }
++    catch (IOException e) {
++      e.printStackTrace();
++    }
++    finally {
++      StudyUtils.closeSilently(reader);
++    }
++    return null;
++  }
++
++  public static void updateAction(AnActionEvent e) {
++    Presentation presentation = e.getPresentation();
++    presentation.setEnabled(false);
++    Project project = e.getProject();
++    if (project != null) {
++      FileEditor[] editors = FileEditorManager.getInstance(project).getAllEditors();
++      for (FileEditor editor : editors) {
++        if (editor instanceof StudyEditor) {
++          presentation.setEnabled(true);
++        }
++      }
++    }
++  }
++
++  public static void updateStudyToolWindow(Project project) {
++    ToolWindowManager.getInstance(project).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW).getContentManager().removeAllContents(false);
++    StudyToolWindowFactory factory =  new StudyToolWindowFactory();
++    factory.createToolWindowContent(project, ToolWindowManager.getInstance(project).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW));
++  }
++
++  public  static void synchronize() {
++    FileDocumentManager.getInstance().saveAllDocuments();
++    SaveAndSyncHandlerImpl.refreshOpenFiles();
++    VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
++  }
++
++  /**
++   * 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.contains(logicalName)) {
++      throw new IllegalArgumentException();
++    }
++    return Integer.parseInt(fullName.substring(logicalName.length())) - 1;
++  }
++
++  public static VirtualFile flushWindows(Document document, TaskFile taskFile, VirtualFile file) {
++    VirtualFile taskDir = file.getParent();
++    VirtualFile file_windows = null;
++    if (taskDir != null) {
++      String name = file.getNameWithoutExtension() + "_windows";
++      PrintWriter printWriter = null;
++      try {
++
++        file_windows = taskDir.createChildData(taskFile, name);
++        printWriter = new PrintWriter(new FileOutputStream(file_windows.getPath()));
++        for (TaskWindow taskWindow : taskFile.getTaskWindows()) {
++          if (!taskWindow.isValid(document)) {
++            continue;
++          }
++          int start = taskWindow.getRealStartOffset(document);
++          String windowDescription = document.getText(new TextRange(start, start + taskWindow.getLength()));
++          printWriter.println("#study_plugin_window = " + windowDescription);
++        }
++      }
++      catch (IOException e) {
++        e.printStackTrace();
++      }
++      finally {
++        closeSilently(printWriter);
++        StudyUtils.synchronize();
++      }
++    }
++    return file_windows;
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..0ab9f129960aa746948cecc7cde361d50b67354b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,283 @@@
++package com.jetbrains.python.edu.actions;
++
++import com.intellij.execution.ExecutionException;
++import com.intellij.execution.configurations.GeneralCommandLine;
++import com.intellij.ide.projectView.ProjectView;
++import com.intellij.openapi.actionSystem.ActionManager;
++import com.intellij.openapi.actionSystem.AnActionEvent;
++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.fileEditor.FileDocumentManager;
++import com.intellij.openapi.module.ModuleManager;
++import com.intellij.openapi.project.DumbAwareAction;
++import com.intellij.openapi.project.Project;
++import com.intellij.openapi.projectRoots.Sdk;
++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.TextRange;
++import com.intellij.openapi.util.io.FileUtil;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.intellij.ui.JBColor;
++import com.jetbrains.python.sdk.PythonSdkType;
++import org.jetbrains.annotations.NotNull;
++import com.jetbrains.python.edu.StudyDocumentListener;
++import com.jetbrains.python.edu.StudyTaskManager;
++import com.jetbrains.python.edu.StudyUtils;
++import com.jetbrains.python.edu.course.*;
++import com.jetbrains.python.edu.editor.StudyEditor;
++
++import javax.swing.*;
++import java.awt.*;
++import java.io.*;
++import java.util.Map;
++
++public class CheckAction extends DumbAwareAction {
++
++  private static final Logger LOG = Logger.getInstance(CheckAction.class.getName());
++
++  class StudyTestRunner {
++    public static final String TEST_OK = "#study_plugin test OK";
++    private static final String TEST_FAILED = "#study_plugin FAILED + ";
++    private final Task myTask;
++    private final VirtualFile myTaskDir;
++
++    StudyTestRunner(Task task, VirtualFile taskDir) {
++      myTask = task;
++      myTaskDir = taskDir;
++    }
++
++    Process launchTests(Project project, String executablePath) throws ExecutionException {
++      Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]);
++      File testRunner = new File(myTaskDir.getPath(), myTask.getTestFile());
++      GeneralCommandLine commandLine = new GeneralCommandLine();
++      commandLine.setWorkDirectory(myTaskDir.getPath());
++      final Map<String, String> env = commandLine.getEnvironment();
++      final VirtualFile courseDir = project.getBaseDir();
++      if (courseDir != null)
++        env.put("PYTHONPATH", courseDir.getPath());
++      if (sdk != null) {
++        String pythonPath = sdk.getHomePath();
++        if (pythonPath != null) {
++          commandLine.setExePath(pythonPath);
++          commandLine.addParameter(testRunner.getPath());
++          final Course course = StudyTaskManager.getInstance(project).getCourse();
++          assert course != null;
++          commandLine.addParameter(new File(course.getResourcePath()).getParent());
++          commandLine.addParameter(executablePath);
++          return commandLine.createProcess();
++        }
++      }
++      return null;
++    }
++
++
++    String getPassedTests(Process p) {
++      InputStream testOutput = p.getInputStream();
++      BufferedReader testOutputReader = new BufferedReader(new InputStreamReader(testOutput));
++      String line;
++      try {
++        while ((line = testOutputReader.readLine()) != null) {
++          if (line.contains(TEST_FAILED)) {
++             return line.substring(TEST_FAILED.length(), line.length());
++          }
++        }
++      }
++      catch (IOException e) {
++        LOG.error(e);
++      }
++      finally {
++        StudyUtils.closeSilently(testOutputReader);
++      }
++      return StudyTestRunner.TEST_OK;
++    }
++  }
++
++  public void check(@NotNull final Project project) {
++    ApplicationManager.getApplication().runWriteAction(new Runnable() {
++      @Override
++      public void run() {
++        CommandProcessor.getInstance().executeCommand(project, new Runnable() {
++          @Override
++          public void run() {
++            final Editor selectedEditor = StudyEditor.getSelectedEditor(project);
++            if (selectedEditor != null) {
++              final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
++              final VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
++              if (openedFile != null) {
++                StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
++                final TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
++                if (selectedTaskFile != null) {
++                  StudyUtils.flushWindows(selectedEditor.getDocument(),selectedTaskFile, openedFile);
++                  FileDocumentManager.getInstance().saveAllDocuments();
++                  final VirtualFile taskDir = openedFile.getParent();
++                  Task currentTask = selectedTaskFile.getTask();
++                  StudyRunAction runAction = (StudyRunAction)ActionManager.getInstance().getAction(StudyRunAction.ACTION_ID);
++                  if (runAction != null) {
++                    runAction.run(project);
++                  }
++                  final StudyTestRunner testRunner = new StudyTestRunner(currentTask, taskDir);
++                  Process testProcess = null;
++                  try {
++                    testProcess = testRunner.launchTests(project, openedFile.getPath());
++                  }
++                  catch (ExecutionException e) {
++                    LOG.error(e);
++                  }
++                  if (testProcess != null) {
++                    String failedMessage = testRunner.getPassedTests(testProcess);
++                    if (failedMessage.equals(StudyTestRunner.TEST_OK)) {
++                      currentTask.setStatus(StudyStatus.Solved);
++                      StudyUtils.updateStudyToolWindow(project);
++                      selectedTaskFile.drawAllWindows(selectedEditor);
++                      ProjectView.getInstance(project).refresh();
++                      createTestResultPopUp("Congratulations!", JBColor.GREEN, project);
++                      return;
++                    }
++
++                    final TaskFile taskFileCopy = new TaskFile();
++                    final VirtualFile copyWithAnswers = getCopyWithAnswers(taskDir, openedFile, selectedTaskFile, taskFileCopy);
++                    for (final TaskWindow taskWindow : taskFileCopy.getTaskWindows()) {
++                      if (!taskWindow.isValid(selectedEditor.getDocument())) {
++                        continue;
++                      }
++                      check(project, taskWindow, copyWithAnswers, taskFileCopy, selectedTaskFile, selectedEditor.getDocument(), testRunner, openedFile);
++                    }
++                    try {
++                      copyWithAnswers.delete(this);
++                    }
++                    catch (IOException e) {
++                      LOG.error(e);
++                    }
++                    selectedTaskFile.drawAllWindows(selectedEditor);
++                    createTestResultPopUp(failedMessage, JBColor.RED, project);
++                  }
++                }
++              }
++            }
++          }
++        }, null, null);
++      }
++    });
++  }
++
++  private void check(Project project,
++                     TaskWindow taskWindow,
++                     VirtualFile answerFile,
++                     TaskFile answerTaskFile,
++                     TaskFile usersTaskFile,
++                     Document usersDocument,
++                     StudyTestRunner testRunner,
++                     VirtualFile openedFile) {
++
++    try {
++      VirtualFile windowCopy = answerFile.copy(this, answerFile.getParent(), "window" + taskWindow.getIndex() + ".py");
++      final FileDocumentManager documentManager = FileDocumentManager.getInstance();
++      final Document windowDocument = documentManager.getDocument(windowCopy);
++      if (windowDocument != null) {
++        StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
++        Course course = taskManager.getCourse();
++        Task task = usersTaskFile.getTask();
++        int taskNum = task.getIndex() + 1;
++        int lessonNum = task.getLesson().getIndex() + 1;
++        assert course != null;
++        String pathToResource = FileUtil.join(new File(course.getResourcePath()).getParent(), Lesson.LESSON_DIR + lessonNum,  Task.TASK_DIR + taskNum);
++        File resourceFile = new File(pathToResource, windowCopy.getName());
++        FileUtil.copy(new File(pathToResource, openedFile.getName()), resourceFile);
++        TaskFile windowTaskFile = new TaskFile();
++        TaskFile.copy(answerTaskFile, windowTaskFile);
++        StudyDocumentListener listener = new StudyDocumentListener(windowTaskFile);
++        windowDocument.addDocumentListener(listener);
++        int start = taskWindow.getRealStartOffset(windowDocument);
++        int end = start + taskWindow.getLength();
++        TaskWindow userTaskWindow = usersTaskFile.getTaskWindows().get(taskWindow.getIndex());
++        int userStart = userTaskWindow.getRealStartOffset(usersDocument);
++        int userEnd = userStart + userTaskWindow.getLength();
++        String text = usersDocument.getText(new TextRange(userStart, userEnd));
++        windowDocument.replaceString(start, end, text);
++        ApplicationManager.getApplication().runWriteAction(new Runnable() {
++          @Override
++          public void run() {
++            documentManager.saveDocument(windowDocument);
++          }
++        });
++        VirtualFile fileWindows = StudyUtils.flushWindows(windowDocument, windowTaskFile, windowCopy);
++        Process smartTestProcess = testRunner.launchTests(project, windowCopy.getPath());
++        boolean res = testRunner.getPassedTests(smartTestProcess).equals(StudyTestRunner.TEST_OK);
++        userTaskWindow.setStatus(res ? StudyStatus.Solved : StudyStatus.Failed);
++        windowCopy.delete(this);
++        fileWindows.delete(this);
++        if (!resourceFile.delete()) {
++          LOG.error("failed to delete", resourceFile.getPath());
++        }
++      }
++    }
++    catch (IOException e) {
++      LOG.error(e);
++    }
++    catch (ExecutionException e) {
++      e.printStackTrace();
++    }
++  }
++
++
++  private VirtualFile getCopyWithAnswers(final VirtualFile taskDir,
++                                         final VirtualFile file,
++                                         final TaskFile source,
++                                         TaskFile target) {
++    VirtualFile copy = null;
++    try {
++
++      copy = file.copy(this, taskDir, "answers.py");
++      final FileDocumentManager documentManager = FileDocumentManager.getInstance();
++      final Document document = documentManager.getDocument(copy);
++      if (document != null) {
++        TaskFile.copy(source, target);
++        StudyDocumentListener listener = new StudyDocumentListener(target);
++        document.addDocumentListener(listener);
++        for (TaskWindow taskWindow : target.getTaskWindows()) {
++          if (!taskWindow.isValid(document)) {
++            continue;
++          }
++          final int start = taskWindow.getRealStartOffset(document);
++          final int end = start + taskWindow.getLength();
++          final String text = taskWindow.getPossibleAnswer();
++          document.replaceString(start, end, text);
++        }
++        ApplicationManager.getApplication().runWriteAction(new Runnable() {
++          @Override
++          public void run() {
++            documentManager.saveDocument(document);
++          }
++        });
++      }
++    }
++    catch (IOException e) {
++      LOG.error(e);
++    }
++
++
++    return copy;
++  }
++
++  private void createTestResultPopUp(final String text, Color color, @NotNull final Project project) {
++    BalloonBuilder balloonBuilder =
++      JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, null, color, null);
++    Balloon balloon = balloonBuilder.createBalloon();
++    StudyEditor studyEditor = StudyEditor.getSelectedStudyEditor(project);
++    assert studyEditor != null;
++    JButton checkButton = studyEditor.getCheckButton();
++    balloon.showInCenterOf(checkButton);
++  }
++
++  @Override
++  public void actionPerformed(AnActionEvent e) {
++    Project project = e.getProject();
++    if (project != null) {
++      check(project);
++    }
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..51fe494552bae53079b2b76f962318f0e05fbc92
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,24 @@@
++package com.jetbrains.python.edu.actions;
++
++import com.jetbrains.python.edu.editor.StudyEditor;
++import com.jetbrains.python.edu.course.Task;
++
++import javax.swing.*;
++
++public class NextTaskAction extends TaskNavigationAction {
++
++  @Override
++  protected JButton getButton(StudyEditor selectedStudyEditor) {
++    return selectedStudyEditor.getNextTaskButton();
++  }
++
++  @Override
++  protected String getNavigationFinishedMessage() {
++    return "It's the last task";
++  }
++
++  @Override
++  protected Task getTargetTask(Task sourceTask) {
++    return sourceTask.next();
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..95abf163a7798899fe857d35400c47356269a46e
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,57 @@@
++package com.jetbrains.python.edu.actions;
++
++import com.intellij.openapi.actionSystem.AnActionEvent;
++import com.intellij.openapi.editor.Editor;
++import com.intellij.openapi.fileEditor.FileDocumentManager;
++import com.intellij.openapi.project.DumbAwareAction;
++import com.intellij.openapi.project.Project;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.jetbrains.python.edu.StudyTaskManager;
++import com.jetbrains.python.edu.StudyUtils;
++import com.jetbrains.python.edu.course.StudyStatus;
++import com.jetbrains.python.edu.course.TaskFile;
++import com.jetbrains.python.edu.course.TaskWindow;
++import com.jetbrains.python.edu.editor.StudyEditor;
++
++/**
++ * move caret to next task window
++ */
++public class NextWindowAction extends DumbAwareAction {
++  public static final String ACTION_ID = "NextWindow";
++  public static final String SHORTCUT = "ctrl pressed PERIOD";
++  public static final String SHORTCUT2 = "ctrl pressed ENTER";
++
++  public void actionPerformed(AnActionEvent e) {
++    Project project = e.getProject();
++    if (project != null) {
++      Editor selectedEditor = StudyEditor.getSelectedEditor(project);
++      if (selectedEditor != null) {
++        FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
++        VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
++        if (openedFile != null) {
++          StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
++          TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
++          if (selectedTaskFile != null) {
++            TaskWindow selectedTaskWindow = selectedTaskFile.getSelectedTaskWindow();
++            boolean ifDraw = false;
++            for (TaskWindow taskWindow : selectedTaskFile.getTaskWindows()) {
++              if (ifDraw) {
++                selectedTaskFile.setSelectedTaskWindow(taskWindow);
++                taskWindow.draw(selectedEditor, taskWindow.getStatus() != StudyStatus.Solved, true);
++                return;
++              }
++              if (taskWindow == selectedTaskWindow) {
++                ifDraw = true;
++              }
++            }
++          }
++        }
++      }
++    }
++  }
++
++  @Override
++  public void update(AnActionEvent e) {
++    StudyUtils.updateAction(e);
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..60a0d606884877f7e0403ffc23c0c003a2956649
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,56 @@@
++package com.jetbrains.python.edu.actions;
++
++import com.intellij.openapi.actionSystem.AnActionEvent;
++import com.intellij.openapi.editor.Editor;
++import com.intellij.openapi.fileEditor.FileDocumentManager;
++import com.intellij.openapi.project.DumbAwareAction;
++import com.intellij.openapi.project.Project;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.jetbrains.python.edu.StudyTaskManager;
++import com.jetbrains.python.edu.StudyUtils;
++import com.jetbrains.python.edu.course.StudyStatus;
++import com.jetbrains.python.edu.course.TaskFile;
++import com.jetbrains.python.edu.course.TaskWindow;
++import com.jetbrains.python.edu.editor.StudyEditor;
++
++/**
++ * author: liana
++ * data: 6/30/14.
++ */
++public class PrevWindowAction extends DumbAwareAction {
++  public static final String ACTION_ID = "PrevWindowAction";
++  public static final String SHORTCUT = "ctrl pressed COMMA";
++  public void actionPerformed(AnActionEvent e) {
++    Project project = e.getProject();
++    assert project != null;
++    Editor selectedEditor = StudyEditor.getSelectedEditor(project);
++    if (selectedEditor != null) {
++      FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
++      VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
++      if (openedFile != null) {
++        StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
++        TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
++        if (selectedTaskFile != null) {
++          TaskWindow selectedTaskWindow = selectedTaskFile.getSelectedTaskWindow();
++          TaskWindow prev = null;
++          for (TaskWindow taskWindow : selectedTaskFile.getTaskWindows()) {
++            if (taskWindow == selectedTaskWindow) {
++              break;
++            }
++            prev = taskWindow;
++          }
++
++          if (prev != null) {
++            selectedTaskFile.setSelectedTaskWindow(prev);
++            prev.draw(selectedEditor, prev.getStatus() != StudyStatus.Solved, true);
++          }
++        }
++      }
++    }
++  }
++
++  @Override
++  public void update(AnActionEvent e) {
++    StudyUtils.updateAction(e);
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4474656f4feae9e10c7bd016237eb933c7224c95
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,25 @@@
++package com.jetbrains.python.edu.actions;
++
++
++import com.jetbrains.python.edu.editor.StudyEditor;
++import com.jetbrains.python.edu.course.Task;
++
++import javax.swing.*;
++
++public class PreviousTaskAction extends TaskNavigationAction {
++
++  @Override
++  protected JButton getButton(StudyEditor selectedStudyEditor) {
++    return selectedStudyEditor.getPrevTaskButton();
++  }
++
++  @Override
++  protected String getNavigationFinishedMessage() {
++    return "It's already the first task";
++  }
++
++  @Override
++  protected Task getTargetTask(Task sourceTask) {
++    return sourceTask.prev();
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..bf521e7d1ad37ae73544c9b099e2030d5c6adb68
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,132 @@@
++package com.jetbrains.python.edu.actions;
++
++import com.intellij.ide.projectView.ProjectView;
++import com.intellij.openapi.actionSystem.AnActionEvent;
++import com.intellij.openapi.application.ApplicationManager;
++import com.intellij.openapi.command.CommandProcessor;
++import com.intellij.openapi.editor.Document;
++import com.intellij.openapi.editor.Editor;
++import com.intellij.openapi.fileEditor.FileDocumentManager;
++import com.intellij.openapi.project.DumbAwareAction;
++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.BalloonBuilder;
++import com.intellij.openapi.ui.popup.JBPopupFactory;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.jetbrains.python.edu.StudyDocumentListener;
++import com.jetbrains.python.edu.StudyTaskManager;
++import com.jetbrains.python.edu.StudyUtils;
++import com.jetbrains.python.edu.course.*;
++import com.jetbrains.python.edu.editor.StudyEditor;
++
++import java.io.*;
++
++/**
++ * author: liana
++ * data: 7/8/14.
++ */
++public class RefreshTaskAction extends DumbAwareAction {
++
++  public void refresh(final Project project) {
++    ApplicationManager.getApplication().invokeLater(new Runnable() {
++      @Override
++      public void run() {
++        ApplicationManager.getApplication().runWriteAction(new Runnable() {
++          @Override
++          public void run() {
++            CommandProcessor.getInstance().executeCommand(project, new Runnable() {
++              @Override
++              public void run() {
++                Editor editor = StudyEditor.getSelectedEditor(project);
++                assert editor != null;
++                Document document = editor.getDocument();
++                StudyDocumentListener listener = StudyEditor.getListener(document);
++                if (listener != null) {
++                  document.removeDocumentListener(listener);
++                }
++                int lineCount = document.getLineCount();
++                if (lineCount != 0) {
++                  document.deleteString(0, document.getLineEndOffset(lineCount - 1));
++                }
++                StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
++                Course course = taskManager.getCourse();
++                assert course != null;
++                File resourceFile = new File(course.getResourcePath());
++                File resourceRoot = resourceFile.getParentFile();
++                FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
++                VirtualFile openedFile = fileDocumentManager.getFile(document);
++                assert openedFile != null;
++                TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
++                assert selectedTaskFile != null;
++                Task currentTask = selectedTaskFile.getTask();
++                String lessonDir = Lesson.LESSON_DIR + String.valueOf(currentTask.getLesson().getIndex() + 1);
++                String taskDir = Task.TASK_DIR + String.valueOf(currentTask.getIndex() + 1);
++                  File pattern = new File(new File(new File(resourceRoot, lessonDir), taskDir), openedFile.getName());
++                  BufferedReader reader = null;
++                  try {
++                    reader = new BufferedReader(new InputStreamReader(new FileInputStream(pattern)));
++                    String line;
++                    StringBuilder patternText = new StringBuilder();
++                    while ((line = reader.readLine()) != null) {
++                      patternText.append(line);
++                      patternText.append("\n");
++                    }
++                    int patternLength = patternText.length();
++                    if (patternText.charAt(patternLength - 1) == '\n') {
++                      patternText.delete(patternLength - 1, patternLength);
++                    }
++                    document.setText(patternText);
++                    StudyStatus oldStatus = currentTask.getStatus();
++                    LessonInfo lessonInfo = currentTask.getLesson().getLessonInfo();
++                    if (oldStatus == StudyStatus.Failed) {
++                      lessonInfo.setTaskFailed(lessonInfo.getTaskFailed() - 1);
++                    }
++                    if (oldStatus == StudyStatus.Solved) {
++                      lessonInfo.setTaskSolved(lessonInfo.getTaskSolved() - 1);
++                    }
++                    lessonInfo.setTaskUnchecked(lessonInfo.getTaskUnchecked() + 1);
++                    StudyUtils.updateStudyToolWindow(project);
++                    for (TaskWindow taskWindow : selectedTaskFile.getTaskWindows()) {
++                      taskWindow.reset();
++                    }
++                    ProjectView.getInstance(project).refresh();
++                    if (listener != null) {
++                      document.addDocumentListener(listener);
++                    }
++                    selectedTaskFile.drawAllWindows(editor);
++                    BalloonBuilder balloonBuilder =
++                      JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can now start again", MessageType.INFO, null);
++                    Balloon balloon = balloonBuilder.createBalloon();
++                    StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
++                    assert selectedStudyEditor != null;
++                    balloon.showInCenterOf(selectedStudyEditor.getRefreshButton());
++                  }
++                  catch (FileNotFoundException e1) {
++                    e1.printStackTrace();
++                  }
++                  catch (IOException e1) {
++                    e1.printStackTrace();
++                  }
++                  finally {
++                    if (reader != null) {
++                      try {
++                        reader.close();
++                      }
++                      catch (IOException e) {
++                        e.printStackTrace();
++                      }
++                    }
++                  }
++              }
++            }, null, null);
++          }
++        });
++      }
++    });
++  }
++
++  public void actionPerformed(AnActionEvent e) {
++    refresh(e.getProject());
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1c2744b0b2738c416d588a2dfe196955b7b793b5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,90 @@@
++package com.jetbrains.python.edu.actions;
++
++import com.intellij.codeInsight.documentation.DocumentationComponent;
++import com.intellij.codeInsight.documentation.DocumentationManager;
++import com.intellij.openapi.actionSystem.AnActionEvent;
++import com.intellij.openapi.editor.Editor;
++import com.intellij.openapi.editor.LogicalPosition;
++import com.intellij.openapi.fileEditor.FileDocumentManager;
++import com.intellij.openapi.project.DumbAwareAction;
++import com.intellij.openapi.project.Project;
++import com.intellij.openapi.ui.popup.JBPopup;
++import com.intellij.openapi.ui.popup.JBPopupFactory;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.intellij.psi.PsiElement;
++import com.intellij.psi.PsiFile;
++import com.intellij.psi.PsiManager;
++import com.jetbrains.python.edu.StudyTaskManager;
++import com.jetbrains.python.edu.StudyUtils;
++import com.jetbrains.python.edu.course.Course;
++import com.jetbrains.python.edu.course.TaskFile;
++import com.jetbrains.python.edu.course.TaskWindow;
++import com.jetbrains.python.edu.editor.StudyEditor;
++
++import java.io.File;
++
++public class ShowHintAction extends DumbAwareAction {
++  public static final String ACTION_ID = "ShowHintAction";
++  public static final String SHORTCUT = "ctrl pressed 7";
++
++  public void actionPerformed(AnActionEvent e) {
++    Project project = e.getProject();
++    if (project != null) {
++      DocumentationManager documentationManager = DocumentationManager.getInstance(project);
++      DocumentationComponent component = new DocumentationComponent(documentationManager);
++      Editor selectedEditor = StudyEditor.getSelectedEditor(project);
++      FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
++      assert selectedEditor != null;
++      VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
++      if (openedFile != null) {
++        StudyTaskManager taskManager = StudyTaskManager.getInstance(e.getProject());
++        TaskFile taskFile = taskManager.getTaskFile(openedFile);
++        if (taskFile != null) {
++          PsiFile file = PsiManager.getInstance(project).findFile(openedFile);
++          if (file != null) {
++            LogicalPosition pos = selectedEditor.getCaretModel().getLogicalPosition();
++            TaskWindow taskWindow = taskFile.getTaskWindow(selectedEditor.getDocument(), pos);
++            if (taskWindow != null) {
++              String hint = taskWindow.getHint();
++              if (hint == null) {
++                return;
++              }
++              Course course = taskManager.getCourse();
++              if (course != null) {
++                File resourceFile = new File(course.getResourcePath());
++                File resourceRoot = resourceFile.getParentFile();
++                if (resourceRoot != null && resourceRoot.exists()) {
++                  File hintsDir = new File(resourceRoot, Course.HINTS_DIR);
++                  if (hintsDir.exists()) {
++                    String hintText = StudyUtils.getFileText(hintsDir.getAbsolutePath(), hint, true);
++                    if (hintText != null) {
++                      int offset = selectedEditor.getDocument().getLineStartOffset(pos.line) + pos.column;
++                      PsiElement element = file.findElementAt(offset);
++                      if (element != null) {
++                        component.setData(element, hintText, true, null);
++                        final JBPopup popup =
++                          JBPopupFactory.getInstance().createComponentPopupBuilder(component, component)
++                            .setDimensionServiceKey(project, DocumentationManager.JAVADOC_LOCATION_AND_SIZE, false)
++                            .setResizable(true)
++                            .setMovable(true)
++                            .setRequestFocus(true)
++                            .createPopup();
++                        component.setHint(popup);
++                        popup.showInBestPositionFor(selectedEditor);
++                      }
++                    }
++                  }
++                }
++              }
++            }
++          }
++        }
++      }
++    }
++  }
++
++  @Override
++  public void update(AnActionEvent e) {
++    StudyUtils.updateAction(e);
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..6b2ba9bc325390b5bad52ba954a3e7c9d2e35950
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,82 @@@
++package com.jetbrains.python.edu.actions;
++
++import com.intellij.execution.ExecutionException;
++import com.intellij.execution.RunContentExecutor;
++import com.intellij.execution.configurations.GeneralCommandLine;
++import com.intellij.execution.process.OSProcessHandler;
++import com.intellij.execution.process.ProcessHandler;
++import com.intellij.openapi.actionSystem.AnActionEvent;
++import com.intellij.openapi.editor.Editor;
++import com.intellij.openapi.fileEditor.FileDocumentManager;
++import com.intellij.openapi.module.ModuleManager;
++import com.intellij.openapi.project.DumbAwareAction;
++import com.intellij.openapi.project.Project;
++import com.intellij.openapi.projectRoots.Sdk;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.jetbrains.python.sdk.PythonSdkType;
++import com.jetbrains.python.edu.StudyResourceManger;
++import com.jetbrains.python.edu.StudyTaskManager;
++import com.jetbrains.python.edu.course.Task;
++import com.jetbrains.python.edu.course.TaskFile;
++import com.jetbrains.python.edu.editor.StudyEditor;
++
++import java.io.File;
++
++public class StudyRunAction extends DumbAwareAction {
++  public static final String ACTION_ID = "StudyRunAction";
++  public void run(Project project) {
++    Editor selectedEditor = StudyEditor.getSelectedEditor(project);
++    FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
++    assert selectedEditor != null;
++    VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
++    StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
++    if (openedFile != null && openedFile.getCanonicalPath() != null) {
++      String filePath = openedFile.getCanonicalPath();
++      GeneralCommandLine cmd = new GeneralCommandLine();
++      cmd.setWorkDirectory(openedFile.getParent().getCanonicalPath());
++      Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]);
++      if (sdk != null) {
++        String pythonPath = sdk.getHomePath();
++        if (pythonPath != null) {
++          cmd.setExePath(pythonPath);
++          TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
++          assert selectedTaskFile != null;
++          Task currentTask = selectedTaskFile.getTask();
++          if (!currentTask.getUserTests().isEmpty()) {
++            cmd.addParameter(new File(project.getBaseDir().getPath(), StudyResourceManger.USER_TESTER).getPath());
++            cmd.addParameter(pythonPath);
++            cmd.addParameter(filePath);
++            Process p = null;
++            try {
++              p = cmd.createProcess();
++            }
++            catch (ExecutionException e) {
++              e.printStackTrace();
++            }
++            ProcessHandler handler = new OSProcessHandler(p);
++
++            RunContentExecutor executor = new RunContentExecutor(project, handler);
++            executor.run();
++            return;
++          }
++          try {
++            cmd.addParameter(filePath);
++            Process p = cmd.createProcess();
++            ProcessHandler handler = new OSProcessHandler(p);
++
++            RunContentExecutor executor = new RunContentExecutor(project, handler);
++            executor.run();
++          }
++
++          catch (ExecutionException e) {
++            e.printStackTrace();
++          }
++        }
++      }
++    }
++  }
++
++  public void actionPerformed(AnActionEvent e) {
++    run(e.getProject());
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d9ca43b3a6ea4707d6d5f3c5926a176b21d02aa8
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,84 @@@
++package com.jetbrains.python.edu.actions;
++
++import com.intellij.openapi.actionSystem.AnActionEvent;
++import com.intellij.openapi.editor.Editor;
++import com.intellij.openapi.fileEditor.FileDocumentManager;
++import com.intellij.openapi.fileEditor.FileEditorManager;
++import com.intellij.openapi.project.DumbAwareAction;
++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.BalloonBuilder;
++import com.intellij.openapi.ui.popup.JBPopupFactory;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.jetbrains.python.edu.StudyTaskManager;
++import com.jetbrains.python.edu.StudyUtils;
++import com.jetbrains.python.edu.course.Lesson;
++import com.jetbrains.python.edu.course.Task;
++import com.jetbrains.python.edu.course.TaskFile;
++import com.jetbrains.python.edu.editor.StudyEditor;
++
++import javax.swing.*;
++import java.util.Map;
++
++/**
++ * author: liana
++ * data: 7/21/14.
++ */
++abstract public class TaskNavigationAction extends DumbAwareAction {
++  public void navigateTask(Project project) {
++    Editor selectedEditor = StudyEditor.getSelectedEditor(project);
++    FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
++    assert selectedEditor != null;
++    VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
++    StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
++    assert openedFile != null;
++    TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
++    assert selectedTaskFile != null;
++    Task currentTask = selectedTaskFile.getTask();
++    Task nextTask = getTargetTask(currentTask);
++    if (nextTask == null) {
++      BalloonBuilder balloonBuilder =
++        JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(getNavigationFinishedMessage(), MessageType.INFO, null);
++      Balloon balloon = balloonBuilder.createBalloon();
++      StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
++      balloon.showInCenterOf(getButton(selectedStudyEditor));
++      return;
++    }
++    for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) {
++      FileEditorManager.getInstance(project).closeFile(file);
++    }
++    int nextTaskIndex = nextTask.getIndex();
++    int lessonIndex = nextTask.getLesson().getIndex();
++    TaskFile nextFile = nextTask.getTaskFiles().values().iterator().next();
++    if (nextFile != null) {
++      VirtualFile projectDir = project.getBaseDir();
++      String lessonDirName = Lesson.LESSON_DIR + String.valueOf(lessonIndex + 1);
++      if (projectDir != null) {
++        VirtualFile lessonDir = projectDir.findChild(lessonDirName);
++        if (lessonDir != null) {
++          String taskDirName = Task.TASK_DIR + String.valueOf(nextTaskIndex + 1);
++          VirtualFile taskDir = lessonDir.findChild(taskDirName);
++          if (taskDir != null) {
++            Map.Entry<String, TaskFile> taskFile = StudyUtils.getFirst(nextTask.getTaskFiles().entrySet());
++            VirtualFile virtualFile = taskDir.findChild(taskFile.getKey());
++            if (virtualFile != null) {
++              FileEditorManager.getInstance(project).openFile(virtualFile, true);
++            }
++          }
++        }
++      }
++    }
++  }
++
++  protected abstract JButton getButton(StudyEditor selectedStudyEditor);
++
++  @Override
++  public void actionPerformed(AnActionEvent e) {
++    navigateTask(e.getProject());
++  }
++
++  protected abstract String getNavigationFinishedMessage();
++
++  protected abstract Task getTargetTask(Task sourceTask);
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9979f6c240f50e9db96ee09a1267216a8c3a6139
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,213 @@@
++package com.jetbrains.python.edu.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 icons.StudyIcons;
++import org.jetbrains.annotations.NotNull;
++import com.jetbrains.python.edu.StudyTaskManager;
++import com.jetbrains.python.edu.StudyUtils;
++import com.jetbrains.python.edu.course.Task;
++import com.jetbrains.python.edu.course.TaskFile;
++import com.jetbrains.python.edu.course.UserTest;
++import com.jetbrains.python.edu.editor.StudyEditor;
++import com.jetbrains.python.edu.ui.TestContentPanel;
++
++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 WatchInputAction extends DumbAwareAction {
++
++  public static final String TEST_TAB_NAME = "test";
++  public static final String USER_TEST_INPUT = "input";
++  public static final String USER_TEST_OUTPUT = "output";
++  private static final Logger LOG = Logger.getInstance(WatchInputAction.class.getName());
++  private JBEditorTabs tabbedPane;
++  private Map<TabInfo, UserTest> myEditableTabs = new HashMap<TabInfo, UserTest>();
++
++  public void showInput(final Project project) {
++    final Editor selectedEditor = StudyEditor.getSelectedEditor(project);
++    if (selectedEditor != null) {
++      FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
++      final VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
++      StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project);
++      assert openedFile != null;
++      TaskFile taskFile = studyTaskManager.getTaskFile(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 = openedFile.getParent();
++            VirtualFile testsDir = taskDir.findChild(Task.USER_TESTS);
++            assert testsDir != null;
++            UserTest userTest = createUserTest(testsDir, currentTask);
++            userTest.setEditable(true);
++            TestContentPanel testContentPanel = new TestContentPanel(userTest);
++            TabInfo testTab = addTestTab(tabbedPane.getTabCount(), testContentPanel, currentTask, true);
++            myEditableTabs.put(testTab, userTest);
++            tabbedPane.addTabSilently(testTab, tabCount - 1);
++            tabbedPane.select(testTab, true);
++          }
++        }
++      });
++      List<UserTest> userTests = currentTask.getUserTests();
++      int i = 1;
++      for (UserTest userTest : userTests) {
++        String inputFileText = StudyUtils.getFileText(null, userTest.getInput(), false);
++        String outputFileText = StudyUtils.getFileText(null, userTest.getOutput(), false);
++        TestContentPanel myContentPanel = new TestContentPanel(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(StudyIcons.Add);
++      tabbedPane.addTabSilently(plusTab, tabbedPane.getTabCount());
++      final JBPopup hint =
++        JBPopupFactory.getInstance().createComponentPopupBuilder(tabbedPane.getComponent(), tabbedPane.getComponent())
++          .setResizable(true)
++          .setMovable(true)
++          .setRequestFocus(true)
++          .createPopup();
++      StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
++      assert selectedStudyEditor != null;
++      hint.showInCenterOf(selectedStudyEditor.getComponent());
++      hint.addListener(new HintClosedListener(currentTask));
++    }
++  }
++
++
++  private void flushBuffer(@NotNull final StringBuilder buffer, @NotNull final File file) {
++    PrintWriter printWriter = null;
++    try {
++      printWriter = new PrintWriter(new FileOutputStream(file));
++      printWriter.print(buffer.toString());
++    }
++    catch (FileNotFoundException e) {
++      LOG.error(e);
++    }
++    finally {
++      StudyUtils.closeSilently(printWriter);
++    }
++    StudyUtils.synchronize();
++  }
++
++  private UserTest createUserTest(@NotNull final VirtualFile testsDir, @NotNull final Task currentTask) {
++    UserTest userTest = new UserTest();
++    List<UserTest> userTests = currentTask.getUserTests();
++    int testNum = userTests.size() + 1;
++    String inputName = USER_TEST_INPUT + testNum;
++    File inputFile = new File(testsDir.getPath(), inputName);
++    String outputName = USER_TEST_OUTPUT + testNum;
++    File outputFile = new File(testsDir.getPath(), outputName);
++    userTest.setInput(inputFile.getPath());
++    userTest.setOutput(outputFile.getPath());
++    userTests.add(userTest);
++    return userTest;
++  }
++
++  private TabInfo addTestTab(int nameIndex, final TestContentPanel contentPanel, @NotNull final Task currentTask, boolean toBeClosable) {
++    TabInfo testTab = toBeClosable ? createClosableTab(contentPanel, currentTask) : new TabInfo(contentPanel);
++    return testTab.setText(TEST_TAB_NAME + String.valueOf(nameIndex));
++  }
++
++  private TabInfo createClosableTab(TestContentPanel 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;
++  }
++
++  public void actionPerformed(AnActionEvent e) {
++    showInput(e.getProject());
++  }
++
++  private class HintClosedListener extends  JBPopupAdapter {
++    private final Task myTask;
++    private HintClosedListener(@NotNull final Task task) {
++      myTask = task;
++    }
++
++    @Override
++    public void onClosed(LightweightWindowEvent event) {
++      for (final UserTest userTest : myTask.getUserTests()) {
++        ApplicationManager.getApplication().runWriteAction(new Runnable() {
++          @Override
++          public void run() {
++            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(tabbedPane.isEditorTabs() ? AllIcons.Actions.CloseNew : AllIcons.Actions.Close);
++      e.getPresentation().setHoveredIcon(tabbedPane.isEditorTabs() ? AllIcons.Actions.CloseNewHovered : AllIcons.Actions.CloseHovered);
++      e.getPresentation().setVisible(UISettings.getInstance().SHOW_CLOSE_BUTTON);
++      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()) {
++        StudyUtils.synchronize();
++      } else {
++        LOG.error("failed to delete user tests");
++      }
++      myTask.getUserTests().remove(userTest);
++    }
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..89613ac7918faf894406325ea7497abe7b429466
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,104 @@@
++package com.jetbrains.python.edu.course;
++
++import com.intellij.openapi.application.ApplicationManager;
++import com.intellij.openapi.diagnostic.Logger;
++import com.intellij.openapi.util.io.FileUtil;
++import com.intellij.openapi.vfs.VirtualFile;
++import org.jetbrains.annotations.NotNull;
++
++import java.io.File;
++import java.io.FilenameFilter;
++import java.io.IOException;
++import java.util.ArrayList;
++import java.util.List;
++
++public class Course {
++
++  private static final Logger LOG = Logger.getInstance(Course.class.getName());
++  public static final String PLAYGROUND_DIR = "Playground";
++  public List<Lesson> lessons = new ArrayList<Lesson>();
++  public String description;
++  public String name;
++  public String myResourcePath = "";
++  public String author;
++  public static final String COURSE_DIR = "course";
++  public static final String HINTS_DIR = "hints";
++
++
++  public List<Lesson> getLessons() {
++    return lessons;
++  }
++
++  /**
++   * Initializes state of course
++   */
++  public void init(boolean isRestarted) {
++    for (Lesson lesson : lessons) {
++      lesson.init(this, isRestarted);
++    }
++  }
++
++  public String getAuthor() {
++    return author;
++  }
++
++  /**
++   * Creates course directory in project user created
++   *
++   * @param baseDir      project directory
++   * @param resourceRoot directory where original course is stored
++   */
++  public void create(@NotNull final VirtualFile baseDir, @NotNull final File resourceRoot) {
++    ApplicationManager.getApplication().invokeLater(
++      new Runnable() {
++        @Override
++        public void run() {
++          ApplicationManager.getApplication().runWriteAction(new Runnable() {
++            @Override
++            public void run() {
++              try {
++                for (int i = 0; i < lessons.size(); i++) {
++                  Lesson lesson = lessons.get(i);
++                  lesson.setIndex(i);
++                  lesson.create(baseDir, resourceRoot);
++                }
++                baseDir.createChildDirectory(this, PLAYGROUND_DIR);
++                File[] files = resourceRoot.listFiles(new FilenameFilter() {
++                  @Override
++                  public boolean accept(File dir, String name) {
++                   return !name.contains(Lesson.LESSON_DIR) && !name.equals("course.json") && !name.equals("hints");
++                  }
++                });
++                for (File file: files) {
++                  FileUtil.copy(file, new File(baseDir.getPath(), file.getName()));
++                }
++              }
++              catch (IOException e) {
++                LOG.error(e);
++              }
++            }
++          });
++        }
++      });
++  }
++
++  public void setName(String name) {
++    this.name = name;
++  }
++
++  public String getName() {
++    return name;
++  }
++
++  public void setResourcePath(@NotNull final String resourcePath) {
++    myResourcePath = resourcePath;
++  }
++
++  public String getResourcePath() {
++    return myResourcePath;
++  }
++
++  public String getDescription() {
++    return description;
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9f820c12c57206ba119a1557410552a26df6c7b5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,52 @@@
++package com.jetbrains.python.edu.course;
++
++/**
++ * Implementation of class which contains information to be shawn in course description in tool window
++ * and when project is being created
++ */
++public class CourseInfo {
++  private String myName;
++  private String myAuthor;
++  private String myDescription;
++  public static CourseInfo INVALID_COURSE = new CourseInfo("", "", "");
++
++  public CourseInfo(String name, String author, String description) {
++    myName = name;
++    myAuthor = author;
++    myDescription = description;
++  }
++
++  public String getName() {
++    return myName;
++  }
++
++  public String getAuthor() {
++    return myAuthor;
++  }
++
++  public String getDescription() {
++    return myDescription;
++  }
++
++  @Override
++  public String toString() {
++    return myName;
++  }
++
++  @Override
++  public boolean equals(Object o) {
++    if (this == o) return true;
++    if (o == null || getClass() != o.getClass()) return false;
++    CourseInfo that = (CourseInfo)o;
++    return that.getName().equals(myName) && that.getAuthor().equals(myAuthor)
++           && that.getDescription().equals(myDescription);
++  }
++
++  @Override
++  public int hashCode() {
++    int result = myName != null ? myName.hashCode() : 0;
++    result = 31 * result + (myAuthor != null ? myAuthor.hashCode() : 0);
++    result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0);
++    return result;
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..84396ea404d439ae3cf4186242980ac3f12c332f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,109 @@@
++package com.jetbrains.python.edu.course;
++
++import com.intellij.openapi.vfs.VirtualFile;
++import com.intellij.util.xmlb.annotations.Transient;
++import org.jetbrains.annotations.NotNull;
++
++import java.io.File;
++import java.io.IOException;
++import java.util.ArrayList;
++import java.util.List;
++
++public class Lesson implements Stateful{
++  public String name;
++  public List<Task> taskList = new ArrayList<Task>();
++  private Course myCourse = null;
++  public int myIndex = -1;
++  public static final String LESSON_DIR = "lesson";
++  public LessonInfo myLessonInfo = new LessonInfo();
++
++  public LessonInfo getLessonInfo() {
++    return myLessonInfo;
++  }
++
++  @Transient
++  public StudyStatus getStatus() {
++    for (Task task : taskList) {
++      StudyStatus taskStatus = task.getStatus();
++      if (taskStatus == StudyStatus.Unchecked || taskStatus == StudyStatus.Failed) {
++        return StudyStatus.Unchecked;
++      }
++    }
++    return StudyStatus.Solved;
++  }
++
++  @Override
++  public void setStatus(StudyStatus status) {
++    for (Task task : taskList) {
++      task.setStatus(status);
++    }
++  }
++
++  public List<Task> getTaskList() {
++    return taskList;
++  }
++
++
++  public String getName() {
++    return name;
++  }
++
++  public void setName(String name) {
++    this.name = name;
++  }
++
++  /**
++   * Creates lesson directory in its course folder in project user created
++   *
++   * @param courseDir    project directory of course
++   * @param resourceRoot directory where original lesson stored
++   * @throws java.io.IOException
++   */
++  public void create(@NotNull final VirtualFile courseDir, @NotNull final File resourceRoot) throws IOException {
++    String lessonDirName = LESSON_DIR + Integer.toString(myIndex + 1);
++    VirtualFile lessonDir = courseDir.createChildDirectory(this, lessonDirName);
++    for (int i = 0; i < taskList.size(); i++) {
++      Task task = taskList.get(i);
++      task.setIndex(i);
++      task.create(lessonDir, new File(resourceRoot, lessonDir.getName()));
++    }
++  }
++
++
++  /**
++   * Initializes state of lesson
++   *
++   * @param course course which lesson belongs to
++   */
++  public void init(final Course course, boolean isRestarted) {
++    myCourse = course;
++    myLessonInfo.setTaskNum(taskList.size());
++    myLessonInfo.setTaskUnchecked(taskList.size());
++    for (Task task : taskList) {
++      task.init(this, isRestarted);
++    }
++  }
++
++  public Lesson next() {
++    List<Lesson> lessons = myCourse.getLessons();
++    if (myIndex + 1 >= lessons.size()) {
++      return null;
++    }
++    return lessons.get(myIndex + 1);
++  }
++
++  public void setIndex(int index) {
++    myIndex = index;
++  }
++
++  public int getIndex() {
++    return myIndex;
++  }
++
++  public Lesson prev() {
++    if (myIndex - 1 < 0) {
++      return null;
++    }
++    return myCourse.getLessons().get(myIndex - 1);
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..9431632c2eddaffe67fe210266aea3b970f80313
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,43 @@@
++package com.jetbrains.python.edu.course;
++
++/**
++ * Implementation of class which contains information about student progress in current lesson
++ */
++public class LessonInfo {
++  private int myTaskNum;
++  private int myTaskFailed;
++  private int myTaskSolved;
++  private int myTaskUnchecked;
++
++  public int getTaskNum() {
++    return myTaskNum;
++  }
++
++  public void setTaskNum(int taskNum) {
++    myTaskNum = taskNum;
++  }
++
++  public int getTaskFailed() {
++    return myTaskFailed;
++  }
++
++  public void setTaskFailed(int taskFailed) {
++    myTaskFailed = taskFailed;
++  }
++
++  public int getTaskSolved() {
++    return myTaskSolved;
++  }
++
++  public void setTaskSolved(int taskSolved) {
++    myTaskSolved = taskSolved;
++  }
++
++  public int getTaskUnchecked() {
++    return myTaskUnchecked;
++  }
++
++  public void setTaskUnchecked(int taskUnchecked) {
++    myTaskUnchecked = taskUnchecked;
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..10374bd94d9af8fe916040f2478ed1a40062d9df
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,6 @@@
++package com.jetbrains.python.edu.course;
++
++public interface Stateful {
++  StudyStatus getStatus();
++  void setStatus(StudyStatus status);
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..d95b42b73866c0f106ad55f568d658383d12bd7f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,8 @@@
++package com.jetbrains.python.edu.course;
++
++/**
++ * @see {@link TaskWindow#myStatus}
++ */
++public enum StudyStatus {
++  Unchecked, Solved, Failed
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..a493b0ebfe8e60ce7dcb5b87af61b6848d1cfcc3
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,208 @@@
++package com.jetbrains.python.edu.course;
++
++import com.intellij.openapi.project.Project;
++import com.intellij.openapi.util.io.FileUtil;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.intellij.util.xmlb.annotations.Transient;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++import com.jetbrains.python.edu.StudyUtils;
++
++import java.io.File;
++import java.io.IOException;
++import java.util.ArrayList;
++import java.util.HashMap;
++import java.util.List;
++import java.util.Map;
++
++/**
++ * Implementation of task which contains task files, tests, input file for tests
++ */
++public class Task implements Stateful{
++  public static final String TASK_DIR = "task";
++  private static final String ourTestFile = "tests.py";
++  public String name;
++  private static final String ourTextFile = "task.html";
++  public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
++  private Lesson myLesson;
++  public int myIndex;
++  public List<UserTest> userTests = new ArrayList<UserTest>();
++  public static final String USER_TESTS = "userTests";
++
++  public Map<String, TaskFile> getTaskFiles() {
++    return taskFiles;
++  }
++
++  @Transient
++  public StudyStatus getStatus() {
++    for (TaskFile taskFile : taskFiles.values()) {
++      StudyStatus taskFileStatus = taskFile.getStatus();
++      if (taskFileStatus == StudyStatus.Unchecked) {
++        return StudyStatus.Unchecked;
++      }
++      if (taskFileStatus == StudyStatus.Failed) {
++        return StudyStatus.Failed;
++      }
++    }
++    return StudyStatus.Solved;
++  }
++
++  public String getName() {
++    return name;
++  }
++
++  public void setName(String name) {
++    this.name = name;
++  }
++
++  public void setStatus(@NotNull final StudyStatus status) {
++    LessonInfo lessonInfo = myLesson.getLessonInfo();
++    StudyStatus oldStatus = getStatus();
++    if (status != oldStatus) {
++      if (status == StudyStatus.Failed) {
++        lessonInfo.setTaskFailed(lessonInfo.getTaskFailed() + 1);
++        lessonInfo.setTaskUnchecked(lessonInfo.getTaskUnchecked() - 1);
++      }
++      if (status == StudyStatus.Solved) {
++        lessonInfo.setTaskSolved(lessonInfo.getTaskSolved() + 1);
++        lessonInfo.setTaskUnchecked(lessonInfo.getTaskUnchecked() - 1);
++      }
++      for (TaskFile taskFile : taskFiles.values()) {
++        taskFile.setStatus(status);
++      }
++    }
++  }
++
++  public List<UserTest> getUserTests() {
++    return userTests;
++  }
++
++  public String getTestFile() {
++    return ourTestFile;
++  }
++
++  public String getText() {
++    return ourTextFile;
++  }
++
++  /**
++   * Creates task directory in its lesson folder in project user created
++   *
++   * @param lessonDir    project directory of lesson which task belongs to
++   * @param resourceRoot directory where original task file stored
++   * @throws java.io.IOException
++   */
++  public void create(@NotNull final VirtualFile lessonDir, @NotNull final File resourceRoot) throws IOException {
++    VirtualFile taskDir = lessonDir.createChildDirectory(this, TASK_DIR + Integer.toString(myIndex + 1));
++    File newResourceRoot = new File(resourceRoot, taskDir.getName());
++    int i = 0;
++    for (Map.Entry<String, TaskFile> taskFile : taskFiles.entrySet()) {
++      TaskFile taskFileContent = taskFile.getValue();
++      taskFileContent.setIndex(i);
++      i++;
++      taskFileContent.create(taskDir, newResourceRoot, taskFile.getKey());
++    }
++    File[] filesInTask = newResourceRoot.listFiles();
++    if (filesInTask != null) {
++      for (File file : filesInTask) {
++        String fileName = file.getName();
++        if (!isTaskFile(fileName)) {
++          File resourceFile = new File(newResourceRoot, fileName);
++          File fileInProject = new File(taskDir.getCanonicalPath(), fileName);
++          FileUtil.copy(resourceFile, fileInProject);
++        }
++      }
++    }
++  }
++
++  private boolean isTaskFile(@NotNull final String fileName) {
++    return taskFiles.get(fileName) != null;
++  }
++
++  @Nullable
++  public TaskFile getFile(@NotNull final String fileName) {
++    return taskFiles.get(fileName);
++  }
++
++  /**
++   * Initializes state of task file
++   *
++   * @param lesson lesson which task belongs to
++   */
++  public void init(final Lesson lesson, boolean isRestarted) {
++    myLesson = lesson;
++    for (TaskFile taskFile : taskFiles.values()) {
++      taskFile.init(this, isRestarted);
++    }
++  }
++
++  public Task next() {
++    Lesson currentLesson = this.myLesson;
++    List<Task> taskList = myLesson.getTaskList();
++    if (myIndex + 1 < taskList.size()) {
++      return taskList.get(myIndex + 1);
++    }
++    Lesson nextLesson = currentLesson.next();
++    if (nextLesson == null) {
++      return null;
++    }
++    return StudyUtils.getFirst(nextLesson.getTaskList());
++  }
++
++  public Task prev() {
++    Lesson currentLesson = this.myLesson;
++    if (myIndex - 1 >= 0) {
++      return myLesson.getTaskList().get(myIndex - 1);
++    }
++    Lesson prevLesson = currentLesson.prev();
++    if (prevLesson == null) {
++      return null;
++    }
++    //getting last task in previous lesson
++    return prevLesson.getTaskList().get(prevLesson.getTaskList().size() - 1);
++  }
++
++  public void setIndex(int index) {
++    myIndex = index;
++  }
++
++  public int getIndex() {
++    return myIndex;
++  }
++
++  public Lesson getLesson() {
++    return myLesson;
++  }
++
++
++  @Nullable
++  public VirtualFile getTaskDir(Project project) {
++    String lessonDirName = Lesson.LESSON_DIR + String.valueOf(myLesson.getIndex() + 1);
++    String taskDirName = TASK_DIR + String.valueOf(myIndex + 1);
++    VirtualFile courseDir = project.getBaseDir();
++    if (courseDir != null) {
++      VirtualFile lessonDir = courseDir.findChild(lessonDirName);
++      if (lessonDir != null) {
++        return lessonDir.findChild(taskDirName);
++      }
++    }
++    return null;
++  }
++
++  /**
++   * Gets text of resource file such as test input file or task text in needed format
++   *
++   * @param fileName name of resource file which should exist in task directory
++   * @param wrapHTML if it's necessary to wrap text with html tags
++   * @return text of resource file wrapped with html tags if necessary
++   */
++  @Nullable
++  public String getResourceText(@NotNull final Project project, @NotNull final String fileName, boolean wrapHTML) {
++    VirtualFile taskDir = getTaskDir(project);
++    if (taskDir != null) {
++      return StudyUtils.getFileText(taskDir.getCanonicalPath(), fileName, wrapHTML);
++    }
++    return null;
++  }
++
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..df3573d753f46538750ca64071eb181964e5822f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,213 @@@
++package com.jetbrains.python.edu.course;
++
++import com.intellij.openapi.editor.Document;
++import com.intellij.openapi.editor.Editor;
++import com.intellij.openapi.editor.LogicalPosition;
++import com.intellij.openapi.util.io.FileUtil;
++import com.intellij.openapi.vfs.VirtualFile;
++import com.intellij.util.xmlb.annotations.Transient;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++import java.io.File;
++import java.io.IOException;
++import java.util.ArrayList;
++import java.util.Collections;
++import java.util.List;
++
++/**
++ * Implementation of task file which contains task windows for student to type in and
++ * which is visible to student in project view
++ */
++
++public class TaskFile implements Stateful{
++  public List<TaskWindow> taskWindows = new ArrayList<TaskWindow>();
++  private Task myTask;
++  @Transient
++  private TaskWindow mySelectedTaskWindow = null;
++  public int myIndex = -1;
++  private boolean myUserCreated = false;
++
++  /**
++   * @return if all the windows in task file are marked as resolved
++   */
++  @Transient
++  public StudyStatus getStatus() {
++    for (TaskWindow taskWindow : taskWindows) {
++      StudyStatus windowStatus = taskWindow.getStatus();
++      if (windowStatus == StudyStatus.Failed) {
++        return StudyStatus.Failed;
++      }
++      if (windowStatus == StudyStatus.Unchecked) {
++        return StudyStatus.Unchecked;
++      }
++    }
++    return StudyStatus.Solved;
++  }
++
++  public Task getTask() {
++    return myTask;
++  }
++
++  @Transient
++  public TaskWindow getSelectedTaskWindow() {
++    return mySelectedTaskWindow;
++  }
++
++  /**
++   * @param selectedTaskWindow window from this task file to be set as selected
++   */
++  public void setSelectedTaskWindow(@NotNull final TaskWindow selectedTaskWindow) {
++    if (selectedTaskWindow.getTaskFile() == this) {
++      mySelectedTaskWindow = selectedTaskWindow;
++    }
++    else {
++      throw new IllegalArgumentException("Window may be set as selected only in task file which it belongs to");
++    }
++  }
++
++  public List<TaskWindow> getTaskWindows() {
++    return taskWindows;
++  }
++
++  /**
++   * Creates task files in its task folder in project user created
++   *
++   * @param taskDir      project directory of task which task file belongs to
++   * @param resourceRoot directory where original task file stored
++   * @throws java.io.IOException
++   */
++  public void create(@NotNull final VirtualFile taskDir, @NotNull final File resourceRoot,
++                     @NotNull final String name) throws IOException {
++    String systemIndependentName = FileUtil.toSystemIndependentName(name);
++    final int index = systemIndependentName.lastIndexOf("/");
++    if (index > 0) {
++      systemIndependentName = systemIndependentName.substring(index + 1);
++    }
++    File resourceFile = new File(resourceRoot, name);
++    File fileInProject = new File(taskDir.getPath(), systemIndependentName);
++    FileUtil.copy(resourceFile, fileInProject);
++  }
++
++  public void drawAllWindows(Editor editor) {
++    for (TaskWindow taskWindow : taskWindows) {
++      taskWindow.draw(editor, false, false);
++    }
++  }
++
++
++  /**
++   * @param pos position in editor
++   * @return task window located in specified position or null if there is no task window in this position
++   */
++  @Nullable
++  public TaskWindow getTaskWindow(@NotNull final Document document, @NotNull final LogicalPosition pos) {
++    int line = pos.line;
++    if (line >= document.getLineCount()) {
++      return null;
++    }
++    int column = pos.column;
++    int offset = document.getLineStartOffset(line) + column;
++    for (TaskWindow tw : taskWindows) {
++      if (tw.getLine() <= line) {
++        int twStartOffset = tw.getRealStartOffset(document);
++        int twEndOffset = twStartOffset + tw.getLength();
++        if (twStartOffset <= offset && offset <= twEndOffset) {
++          return tw;
++        }
++      }
++    }
++    return null;
++  }
++
++  /**
++   * Updates task window lines
++   *
++   * @param startLine lines greater than this line and including this line will be updated
++   * @param change    change to be added to line numbers
++   */
++  public void incrementLines(int startLine, int change) {
++    for (TaskWindow taskTaskWindow : taskWindows) {
++      if (taskTaskWindow.getLine() >= startLine) {
++        taskTaskWindow.setLine(taskTaskWindow.getLine() + change);
++      }
++    }
++  }
++
++  /**
++   * Initializes state of task file
++   *
++   * @param task task which task file belongs to
++   */
++
++  public void init(final Task task, boolean isRestarted) {
++    myTask = task;
++    for (TaskWindow taskWindow : taskWindows) {
++      taskWindow.init(this, isRestarted);
++    }
++    Collections.sort(taskWindows);
++    for (int i = 0; i < taskWindows.size(); i++) {
++      taskWindows.get(i).setIndex(i);
++    }
++  }
++
++  /**
++   * @param index index of task file in list of task files of its task
++   */
++  public void setIndex(int index) {
++    myIndex = index;
++  }
++
++  /**
++   * Updates windows in specific line
++   *
++   * @param lineChange         change in line number
++   * @param line               line to be updated
++   * @param newEndOffsetInLine distance from line start to end of inserted fragment
++   * @param oldEndOffsetInLine distance from line start to end of changed fragment
++   */
++  public void updateLine(int lineChange, int line, int newEndOffsetInLine, int oldEndOffsetInLine) {
++    for (TaskWindow w : taskWindows) {
++      if ((w.getLine() == line) && (w.getStart() >= oldEndOffsetInLine)) {
++        int distance = w.getStart() - oldEndOffsetInLine;
++        if (lineChange != 0 || newEndOffsetInLine <= w.getStart()) {
++          w.setStart(distance + newEndOffsetInLine);
++          w.setLine(line + lineChange);
++        }
++      }
++    }
++  }
++
++  public static void copy(@NotNull final TaskFile source, @NotNull final TaskFile target) {
++    List<TaskWindow> sourceTaskWindows = source.getTaskWindows();
++    List<TaskWindow> windowsCopy = new ArrayList<TaskWindow>(sourceTaskWindows.size());
++    for (TaskWindow taskWindow : sourceTaskWindows) {
++      TaskWindow taskWindowCopy = new TaskWindow();
++      taskWindowCopy.setLine(taskWindow.getLine());
++      taskWindowCopy.setStart(taskWindow.getStart());
++      taskWindowCopy.setLength(taskWindow.getLength());
++      taskWindowCopy.setPossibleAnswer(taskWindow.getPossibleAnswer());
++      taskWindowCopy.setIndex(taskWindow.getIndex());
++      windowsCopy.add(taskWindowCopy);
++    }
++    target.setTaskWindows(windowsCopy);
++  }
++
++  public void setTaskWindows(List<TaskWindow> taskWindows) {
++    this.taskWindows = taskWindows;
++  }
++
++  public void setStatus(@NotNull final StudyStatus status) {
++    for (TaskWindow taskWindow : taskWindows) {
++      taskWindow.setStatus(status);
++    }
++  }
++
++  public void setUserCreated(boolean userCreated) {
++    myUserCreated = userCreated;
++  }
++
++  public boolean isUserCreated() {
++    return myUserCreated;
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..04e65c3abb59aac8a1974fcbcaef23ce5004489f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,177 @@@
++package com.jetbrains.python.edu.course;
++
++import com.intellij.openapi.editor.Document;
++import com.intellij.openapi.editor.Editor;
++import com.intellij.openapi.editor.colors.EditorColors;
++import com.intellij.openapi.editor.colors.EditorColorsManager;
++import com.intellij.openapi.editor.markup.HighlighterLayer;
++import com.intellij.openapi.editor.markup.HighlighterTargetArea;
++import com.intellij.openapi.editor.markup.RangeHighlighter;
++import com.intellij.openapi.editor.markup.TextAttributes;
++import com.intellij.ui.JBColor;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Implementation of windows which user should type in
++ */
++
++
++public class TaskWindow implements Comparable, Stateful {
++
++  public int line = 0;
++  public int start = 0;
++  public String hint = "";
++  public String possibleAnswer = "";
++  public int length = 0;
++  private TaskFile myTaskFile;
++  public int myIndex = -1;
++  public int myInitialLine = -1;
++  public int myInitialStart = -1;
++  public int myInitialLength = -1;
++  private StudyStatus myStatus = StudyStatus.Unchecked;
++
++  public StudyStatus getStatus() {
++    return myStatus;
++  }
++
++  public void setStatus(StudyStatus status) {
++    myStatus = status;
++  }
++
++  public void setIndex(int index) {
++    myIndex = index;
++  }
++
++  public int getLength() {
++    return length;
++  }
++
++  public void setLength(int length) {
++    this.length = length;
++  }
++
++  public int getStart() {
++    return start;
++  }
++
++  public void setStart(int start) {
++    this.start = start;
++  }
++
++  public void setLine(int line) {
++    this.line = line;
++  }
++
++  public int getLine() {
++    return line;
++  }
++
++
++  /**
++   * Draw task window with color according to its status
++   */
++  public void draw(@NotNull final Editor editor, boolean drawSelection, boolean moveCaret) {
++    Document document = editor.getDocument();
++    if (!isValid(document)) {
++      return;
++    }
++    TextAttributes defaultTestAttributes =
++      EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.LIVE_TEMPLATE_ATTRIBUTES);
++    JBColor color = getColor();
++    int startOffset = document.getLineStartOffset(line) + start;
++    RangeHighlighter
++      rh = editor.getMarkupModel().addRangeHighlighter(startOffset, startOffset + length, HighlighterLayer.LAST + 1,
++                                                       new TextAttributes(defaultTestAttributes.getForegroundColor(),
++                                                                          defaultTestAttributes.getBackgroundColor(), color,
++                                                                          defaultTestAttributes.getEffectType(),
++                                                                          defaultTestAttributes.getFontType()),
++                                                       HighlighterTargetArea.EXACT_RANGE);
++    if (drawSelection) {
++      editor.getSelectionModel().setSelection(startOffset, startOffset + length);
++    }
++    if (moveCaret) {
++      editor.getCaretModel().moveToOffset(startOffset);
++    }
++    rh.setGreedyToLeft(true);
++    rh.setGreedyToRight(true);
++  }
++
++  public boolean isValid(@NotNull final Document document) {
++    boolean isLineValid = line < document.getLineCount() && line >= 0;
++    if (!isLineValid) return false;
++    boolean isStartValid = start >= 0 && start < document.getLineEndOffset(line);
++    boolean isLengthValid = (getRealStartOffset(document) + length) <= document.getTextLength();
++    return isLengthValid && isStartValid;
++  }
++
++  private JBColor getColor() {
++    if (myStatus == StudyStatus.Solved) {
++      return JBColor.GREEN;
++    }
++    if (myStatus == StudyStatus.Failed) {
++      return JBColor.RED;
++    }
++    return JBColor.BLUE;
++  }
++
++  public int getRealStartOffset(@NotNull final Document document) {
++    return document.getLineStartOffset(line) + start;
++  }
++
++  /**
++   * Initializes window
++   *
++   * @param file task file which window belongs to
++   */
++  public void init(final TaskFile file, boolean isRestarted) {
++    if (!isRestarted) {
++      myInitialLine = line;
++      myInitialLength = length;
++      myInitialStart = start;
++    }
++    myTaskFile = file;
++  }
++
++  public TaskFile getTaskFile() {
++    return myTaskFile;
++  }
++
++  @Override
++  public int compareTo(@NotNull Object o) {
++    TaskWindow taskWindow = (TaskWindow)o;
++    if (taskWindow.getTaskFile() != myTaskFile) {
++      throw new ClassCastException();
++    }
++    int lineDiff = line - taskWindow.line;
++    if (lineDiff == 0) {
++      return start - taskWindow.start;
++    }
++    return lineDiff;
++  }
++
++  /**
++   * Returns window to its initial state
++   */
++  public void reset() {
++    myStatus = StudyStatus.Unchecked;
++    line = myInitialLine;
++    start = myInitialStart;
++    length = myInitialLength;
++  }
++
++  public String getHint() {
++    return hint;
++  }
++
++  public String getPossibleAnswer() {
++    return possibleAnswer;
++  }
++
++  public void setPossibleAnswer(String possibleAnswer) {
++    this.possibleAnswer = possibleAnswer;
++  }
++
++  public int getIndex() {
++    return myIndex;
++  }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..8133e9102a141dcadfe97112b58b5f65a52a4dc6
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,41 @@@
++package com.jetbrains.python.edu.course;
++
++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 --cc