diff options
Diffstat (limited to 'python/edu')
112 files changed, 3745 insertions, 424 deletions
diff --git a/python/edu/build/desktop.ini b/python/edu/build/desktop.ini index f56d43c998bf..0ea32dec3569 100644 --- a/python/edu/build/desktop.ini +++ b/python/edu/build/desktop.ini @@ -28,7 +28,7 @@ Text=Choice Python version [Field 4] Type=RadioButton Left=5 -Right=45 +Right=100 Top=50 Bottom=60 State=1 @@ -36,8 +36,8 @@ Text=Python 2 [Field 5] Type=RadioButton -Left=95 -Right=135 +Left=120 +Right=-1 Top=50 Bottom=60 State=0 diff --git a/python/edu/build/idea.nsi b/python/edu/build/idea.nsi index d9903c94de5e..05c37577893b 100644 --- a/python/edu/build/idea.nsi +++ b/python/edu/build/idea.nsi @@ -23,6 +23,7 @@ RequestExecutionLevel user ;------------------------------------------------------------------------------ !include "MUI2.nsh" !include "FileFunc.nsh" +!include "TextFunc.nsh" !include UAC.nsh !include "InstallOptions.nsh" !include StrFunc.nsh @@ -32,6 +33,7 @@ ${UnStrStr} ${UnStrLoc} ${UnStrRep} ${StrRep} +${StrTok} ReserveFile "desktop.ini" ReserveFile "DeleteSettings.ini" @@ -700,49 +702,69 @@ command_exists: '$INSTDIR\bin\${PRODUCT_EXE_FILE} "%1"' FunctionEnd +Function getPythonInfo + ClearErrors + FileOpen $3 $INSTDIR\python\python.txt r + IfErrors cantOpenFile ;file can not be open. + ;get python2 info + FileRead $3 $4 + ${StrTok} $0 $4 " " "1" "1" + ${StrTok} $1 $4 " " "2" "1" + ;get python3 info + FileRead $3 $4 + ${StrTok} $R0 $4 " " "1" "1" + ${StrTok} $R1 $4 " " "2" "1" + goto Done +cantOpenFile: + MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is not exist. Python will not be downloaded." + StrCpy $0 "Error" +Done: +FunctionEnd + + ;------------------------------------------------------------------------------ ; Installer sections ;------------------------------------------------------------------------------ Section "IDEA Files" CopyIdeaFiles -; StrCpy $baseRegKey "HKCU" -; !insertmacro INSTALLOPTIONS_READ $R2 "Desktop.ini" "Field 3" "State" -; StrCmp $R2 1 continue_for_current_user -; SetShellVarContext all -; StrCpy $baseRegKey "HKLM" -; continue_for_current_user: - -; create shortcuts - + ${LineSum} "$INSTDIR\python\python.txt" $R0 + IfErrors cantOpenFile + StrCmp $R0 "2" getPythonInfo ;info about 2 and 3 version of python +cantOpenFile: + MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is invalid. Python will not be downloaded." + goto skip_python_download +getPythonInfo: + Call getPythonInfo + StrCmp $0 "Error" skip_python_download !insertmacro INSTALLOPTIONS_READ $R2 "Desktop.ini" "Field 4" "State" StrCmp $R2 1 "" python3 - StrCpy $R2 "2.7" + StrCpy $R2 $0 + StrCpy $R3 $1 goto check_python python3: - StrCpy $R2 "3.4" + StrCpy $R2 $R0 + StrCpy $R3 $R1 check_python: - ReadRegStr $1 "HKCU" "Software\Python\PythonCore\$R2\InstallPath" $0 + ReadRegStr $1 "HKCU" "Software\Python\PythonCore\$R2\InstallPath" "" StrCmp $1 "" installation_for_all_users goto verefy_python_launcher installation_for_all_users: - ReadRegStr $1 "HKLM" "Software\Python\PythonCore\$R2\InstallPath" $0 + ReadRegStr $1 "HKLM" "Software\Python\PythonCore\$R2\InstallPath" "" StrCmp $1 "" get_python verefy_python_launcher: IfFileExists $1\python.exe python_exists get_python - -get_python: - CreateDirectory "$INSTDIR\python" - StrCmp $R2 "2.7" get_python2 - inetc::get "https://www.python.org/ftp/python/3.4.1/python-3.4.1.amd64.msi" "$INSTDIR\python\python_$R2.msi" +get_python: + inetc::get "$R3" "$INSTDIR\python\python_$R2.msi" goto validate_download -get_python2: - inetc::get "http://www.python.org/ftp/python/2.7.8/python-2.7.8.msi" "$INSTDIR\python\python_$R2.msi" -validate_download: +validate_download: Pop $0 ${If} $0 == "OK" - ExecCmd::exec 'msiexec /i "$INSTDIR\python\python_$R2.msi" /quiet /qn /norestart /log "$INSTDIR\python\python_$R2_silent.log"' + ExecCmd::exec 'msiexec /i "$INSTDIR\python\python_$R2.msi" /quiet /qn /norestart' + ${Else} + MessageBox MB_OK|MB_ICONEXCLAMATION "The download is failed" ${EndIf} - python_exists: +skip_python_download: +; create shortcuts !insertmacro INSTALLOPTIONS_READ $R2 "Desktop.ini" "Field 1" "State" StrCmp $R2 1 "" skip_desktop_shortcut CreateShortCut "$DESKTOP\${PRODUCT_FULL_NAME_WITH_VER}.lnk" \ @@ -882,6 +904,21 @@ Function ConfirmDesktopShortcut ${If} $0 == "1" !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 2" "Flags" "DISABLED" ${EndIf} + CreateDirectory "$INSTDIR\python" + inetc::get "http://www.jetbrains.com/updates/python.txt" "$INSTDIR\python\python.txt" + ${LineSum} "$INSTDIR\python\python.txt" $R0 + IfErrors cantOpenFile + StrCmp $R0 "2" getPythonInfo +cantOpenFile: + MessageBox MB_OK|MB_ICONEXCLAMATION "python.txt is not exist. Python will not be downloaded." + goto association +getPythonInfo: + Call getPythonInfo + StrCmp $0 "Error" association + !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 4" "Text" "Python $0" + !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 5" "Text" "Python $R0" + +association: StrCmp "${ASSOCIATION}" "NoAssociation" skip_association StrCpy $R0 6 push "${ASSOCIATION}" diff --git a/python/edu/build/paths.nsi b/python/edu/build/paths.nsi index 910f2b3555b7..d85492988c41 100644 --- a/python/edu/build/paths.nsi +++ b/python/edu/build/paths.nsi @@ -1,5 +1,5 @@ ; Installer images -!define IMAGES_LOCATION ${COMMUNITY_DIR}\python\build\resources +!define IMAGES_LOCATION ${COMMUNITY_DIR}\python\edu\build\resources ;!define LICENSE_FILE ${BASE_DIR}\python\license\PyCharm_Preview_License !define PRODUCT_PROPERTIES_FILE ${BASE_DIR}\out\pycharmEDU\layout\bin\idea.properties !define PRODUCT_VM_OPTIONS_NAME pycharm.exe.vmoptions diff --git a/python/edu/build/plugin-list.txt b/python/edu/build/plugin-list.txt index bc4b94c9e7f4..60ecf3ff6f2d 100644 --- a/python/edu/build/plugin-list.txt +++ b/python/edu/build/plugin-list.txt @@ -4,4 +4,5 @@ remote-servers-git github IntelliLang IntelliLang-python -learn-python
\ No newline at end of file +learn-python +course-creator
\ No newline at end of file diff --git a/python/edu/build/pycharm_edu_build.gant b/python/edu/build/pycharm_edu_build.gant index 2ef0bed607a7..28d829e68b34 100644 --- a/python/edu/build/pycharm_edu_build.gant +++ b/python/edu/build/pycharm_edu_build.gant @@ -22,14 +22,17 @@ includeTargets << new File("$home/community/build/scripts/libLicenses.gant") includeTargets << new File("$home/build/scripts/ultimate_utils.gant") requireProperty("buildNumber", requireProperty("build.number", snapshot)) -setProperty("buildName", "PE-$buildNumber") +setProperty("buildName", "EDU-$buildNumber") setProperty("ch", "$home/community") setProperty("pythonCommunityHome", "$ch/python") setProperty("pythonEduHome", "$ch/python/edu") +requireProperty("jdk_bundled_mac", "1.7") +def jdk_bundled_version = p("jdk_bundled_mac") == "1.8" ? "jdk8_mac_redist.tar" : "jdk_mac_redist.tar" +ant.copy(file: "${home}/build/jdk/${jdk_bundled_version}", tofile: "${home}/build/jdk/jdk_mac_redist_for_${buildNumber}.tar") // load ApplicationInfo.xml properties ant.xmlproperty(file: "$pythonEduHome/resources/idea/PyCharmEduApplicationInfo.xml", collapseAttributes: "true") -setProperty("system_selector", "PyCharm${p("component.version.major")}0") +setProperty("system_selector", "PyCharmEdu${p("component.version.major")}0") setProperty("dryRun", false) setProperty("jdk16", guessJdk()) @@ -89,7 +92,6 @@ class Paths { } setProperty("paths", new Paths(home)) -setProperty("buildName", "PE-$buildNumber") target('default': "Build artifacts") { @@ -196,12 +198,12 @@ public layoutEducational(String classesPath, Set usedJars) { buildNSIS([paths.distAll, paths.distWin], "$pythonEduHome/build/strings.nsi", "$pythonEduHome/build/paths.nsi", - "pycharmPE-", false, true, ".py", system_selector) + "pycharmEDU-", false, true, ".py", system_selector) - String tarRoot = isEap() ? "pycharm-pe-$buildNumber" : "pycharm-pe-${p("component.version.major")}.${p("component.version.minor")}" + String tarRoot = isEap() ? "pycharm-edu-$buildNumber" : "pycharm-edu-${p("component.version.major")}.${p("component.version.minor")}" buildTarGz(tarRoot, "$paths.artifacts/pycharm${buildName}.tar", [paths.distAll, paths.distUnix]) - String macAppRoot = isEap() ? "PyCharm PE ${p("component.version.major")}.${p("component.version.minor")} EAP.app/Contents" : "PyCharm PE.app/Contents" + String macAppRoot = isEap() ? "PyCharm Educational ${p("component.version.major")}.${p("component.version.minor")} EAP.app/Contents" : "PyCharm Educational.app/Contents" buildMacZip(macAppRoot, "${paths.artifacts}/pycharm${buildName}.sit", [paths.distAll], paths.distMac) ant.copy(file: "${paths.artifacts}/pycharm${buildName}.sit", tofile: "${paths.artifacts}/pycharm${buildName}-jdk-bundled.sit") ant.delete(file: "${paths.artifacts}/pycharm${buildName}.sit") @@ -214,6 +216,7 @@ private layoutPlugins(layouts) { fileset(dir: "$pythonEduHome/learn-python/resources/courses") } } + layouts.layoutPlugin("course-creator") } layouts.layoutCommunityPlugins(ch) @@ -363,7 +366,7 @@ private layoutWin(Map args, String target) { winScripts(target, ch, "pycharm.bat", args) winVMOptions(target, null, "pycharm.exe") - ant.copy(file: "$home/python/help/pycharmhelp.jar", todir: "$target/help", failonerror: false) + ant.copy(file: "$home/python/help/pycharm-eduhelp.jar", todir: "$target/help", failonerror: false) } private layoutUnix(Map args, String target) { @@ -380,7 +383,7 @@ private layoutUnix(Map args, String target) { unixScripts(target, ch, "pycharm.sh", args) unixVMOptions(target, "pycharm") - ant.copy(file: "$home/python/help/pycharmhelp.jar", todir: "$target/help", failonerror: false) + ant.copy(file: "$home/python/help/pycharm-eduhelp.jar", todir: "$target/help", failonerror: false) } private layoutMac(Map _args, String target) { diff --git a/python/edu/build/python.txt b/python/edu/build/python.txt new file mode 100644 index 000000000000..d6f247d29afb --- /dev/null +++ b/python/edu/build/python.txt @@ -0,0 +1,2 @@ +python2 2.7 https://www.python.org/ftp/python/2.7.8/python-2.7.8.amd64.msi +python3 3.4 https://www.python.org/ftp/python/3.4.1/python-3.4.1.amd64.msi
\ No newline at end of file diff --git a/python/edu/build/resources/logo.bmp b/python/edu/build/resources/logo.bmp Binary files differnew file mode 100644 index 000000000000..9ff6698b8e36 --- /dev/null +++ b/python/edu/build/resources/logo.bmp diff --git a/python/edu/build/resources/logo.png b/python/edu/build/resources/logo.png Binary files differdeleted file mode 100644 index 1ccbc0773837..000000000000 --- a/python/edu/build/resources/logo.png +++ /dev/null diff --git a/python/edu/build/upload_pythonInfo.xml b/python/edu/build/upload_pythonInfo.xml new file mode 100644 index 000000000000..f8d9477d1a3f --- /dev/null +++ b/python/edu/build/upload_pythonInfo.xml @@ -0,0 +1,32 @@ +<project name="Upload updates.xml to jetbrains.com. Effective in half an hour" default="bootstrap"> + <property name="home" value="${basedir}/../../../.."/> + <target name="upload"> + <xmlvalidate file="${home}/build/eap/updates.xml"/> + + <property name="host" value="ftp.labs.intellij.net"/> + <property name="user" value="idea"/> + <property name="password" value="4pawoMauoJjjlxpIl3XG"/> + + <ftp server="${host}" action="send" binary="false" remotedir="updates" userid="${user}" password="${password}"> + <fileset file="${home}/community/python/edu/build/python.txt"/> + </ftp> + </target> + + <target name="bootstrap"> + <java failonerror="true" classname="org.apache.tools.ant.Main" fork="true"> + <classpath> + <fileset dir="${home}/community/lib/ant/lib"> + <include name="*.jar"/> + </fileset> + <fileset dir="${home}/community/lib"> + <include name="commons-net-3.1.jar"/> + <include name="jsch-0.1.50.jar"/> + </fileset> + </classpath> + + <arg value="-f"/> + <arg value="${ant.file}"/> + <arg value="upload"/> + </java> + </target> +</project> diff --git a/python/edu/course-creator/course-creator.iml b/python/edu/course-creator/course-creator.iml new file mode 100644 index 000000000000..fc8c6a1462b1 --- /dev/null +++ b/python/edu/course-creator/course-creator.iml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module" module-name="python-community" /> + <orderEntry type="module" module-name="lang-impl" /> + <orderEntry type="library" name="gson" level="project" /> + </component> +</module> + diff --git a/python/edu/course-creator/resources/META-INF/plugin.xml b/python/edu/course-creator/resources/META-INF/plugin.xml new file mode 100644 index 000000000000..6a9a9ea90276 --- /dev/null +++ b/python/edu/course-creator/resources/META-INF/plugin.xml @@ -0,0 +1,60 @@ +<idea-plugin version="2"> + <id>org.jetbrains.plugins.coursecreator</id> + <name>Course Creator for PyCharm Educational</name> + <version>1.0</version> + + <description><![CDATA[ + Plugin allows you to create new course for PyCharm Education Edition. + ]]></description> + + <change-notes><![CDATA[ + ]]> + </change-notes> + <!-- please see http://confluence.jetbrains.com/display/IDEADEV/Build+Number+Ranges for description --> + + <!-- please see http://confluence.jetbrains.com/display/IDEADEV/Plugin+Compatibility+with+IntelliJ+Platform+Products + on how to target different products --> + <!-- uncomment to enable plugin in all products + <depends>com.intellij.modules.lang</depends> + --> + + <depends>com.intellij.modules.python</depends> + + <extensions defaultExtensionNs="com.intellij"> + <directoryProjectGenerator implementation="org.jetbrains.plugins.coursecreator.CCProjectGenerator"/> + <projectService serviceImplementation="org.jetbrains.plugins.coursecreator.CCProjectService"/> + <codeInsight.lineMarkerProvider language="Python" + implementationClass="org.jetbrains.plugins.coursecreator.highlighting.CCTaskLineMarkerProvider"/> + <treeStructureProvider implementation="org.jetbrains.plugins.coursecreator.projectView.CCTreeStructureProvider"/> + </extensions> + + <application-components> + <!-- Add your application components here --> + </application-components> + + <project-components> + <!-- Add your project components here --> + <component> + <implementation-class>org.jetbrains.plugins.coursecreator.CCProjectComponent</implementation-class> + </component> + </project-components> + + <actions> + <action id="CreateLesson" class="org.jetbrains.plugins.coursecreator.actions.CreateLesson"> + <add-to-group group-id="NewGroup" anchor="before" relative-to-action="NewFile"/> + </action> + <action id="CreateTaskFile" class="org.jetbrains.plugins.coursecreator.actions.CreateTaskFile"> + <add-to-group group-id="NewGroup" anchor="before" relative-to-action="NewFile"/> + </action> + <action id="CreateTask" class="org.jetbrains.plugins.coursecreator.actions.CreateTask"> + <add-to-group group-id="NewGroup" anchor="before" relative-to-action="NewFile"/> + </action> + <action id="AddTaskWindow" class="org.jetbrains.plugins.coursecreator.actions.AddTaskWindow"> + <add-to-group group-id="EditorPopupMenu" anchor="before" relative-to-action="CopyReference"/> + </action> + <action id="PackCourse" class="org.jetbrains.plugins.coursecreator.actions.CreateCourseArchive"> + <add-to-group group-id="MainToolBar" anchor="last" /> + </action> + </actions> + +</idea-plugin>
\ No newline at end of file diff --git a/python/edu/course-creator/resources/fileTemplates/internal/task.html.ft b/python/edu/course-creator/resources/fileTemplates/internal/task.html.ft new file mode 100644 index 000000000000..f6833496d1c1 --- /dev/null +++ b/python/edu/course-creator/resources/fileTemplates/internal/task.html.ft @@ -0,0 +1,4 @@ +<html> +Write your task text here. +<br> +</html>
\ No newline at end of file diff --git a/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft b/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft new file mode 100644 index 000000000000..0256e108786c --- /dev/null +++ b/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft @@ -0,0 +1 @@ +# TODO: type solution here
\ No newline at end of file diff --git a/python/edu/course-creator/resources/fileTemplates/internal/test_helper.py.ft b/python/edu/course-creator/resources/fileTemplates/internal/test_helper.py.ft new file mode 100644 index 000000000000..1182b7815c06 --- /dev/null +++ b/python/edu/course-creator/resources/fileTemplates/internal/test_helper.py.ft @@ -0,0 +1,127 @@ +import sys + +def get_file_text(path): + """ get file text by path""" + file_io = open(path, "r") + text = file_io.read() + file_io.close() + return text + +def get_file_output(path): + # TODO: get file output by path + return "" + +def test_file_importable(): + """ tests there is no obvious syntax errors""" + path = sys.argv[-1] + try: + import_file(path) + except ImportError: + failed("File contains syntax errors") + return + except SyntaxError: + failed("File contains syntax errors") + return + except NameError: + failed("File contains syntax errors") + return + + passed() + +def import_file(path): + """ returns imported file """ + import imp + tmp = imp.load_source('tmp', path) + return tmp + +def import_task_file(): + """ returns imported file """ + path = sys.argv[-1] + return import_file(path) + +def test_is_not_empty(): + path = sys.argv[-1] + file_text = get_file_text(path) + + if len(file_text) > 0: + passed() + else: + failed("The file is empty. Please, reload the task and try again.") + +def test_is_initial_text(error_text="You should modify the file"): + path = sys.argv[-1] + text = get_initial_text(path) + file_text = get_file_text(path) + + if file_text.strip() == text: + failed(error_text) + else: + passed() + +def get_initial_text(path): + course_lib = sys.argv[-2] + + import os + # path format is "project_root/lessonX/taskY/file.py" + task_index = path.rfind(os.sep, 0, path.rfind(os.sep)) + index = path.rfind(os.sep, 0, task_index) + relative_path = path[index+1:] + initial_file_path = os.path.join(course_lib, relative_path) + return get_file_text(initial_file_path) + + +def test_text_equals(text, error_text): + path = sys.argv[-1] + file_text = get_file_text(path) + + if file_text.strip() == text: + passed() + else: + failed(error_text) + +def test_window_text_deleted(error_text="Don't just delete task text"): + windows = get_task_windows() + + for window in windows: + if len(window) == 0: + failed(error_text) + return + passed() + + +def failed(message="Please, reload the task and try again."): + print("#study_plugin FAILED + " + message) + +def passed(): + print("#study_plugin test OK") + +def get_task_windows(): + prefix = "#study_plugin_window = " + path = sys.argv[-1] + import os + windows_path = os.path.splitext(path)[0] + "_windows" + windows = [] + f = open(windows_path, "r") + window_text = "" + first = True + for line in f.readlines(): + if line.startswith(prefix): + if not first: + windows.append(window_text.strip()) + else: + first = False + window_text = line[len(prefix):] + else: + window_text += line + + if window_text: + windows.append(window_text.strip()) + + f.close() + return windows + +def run_common_tests(error_text="Please, reload file and try again"): + test_file_importable() + test_is_not_empty() + test_is_initial_text(error_text) + test_window_text_deleted(error_text)
\ No newline at end of file diff --git a/python/edu/course-creator/resources/fileTemplates/internal/tests.py.ft b/python/edu/course-creator/resources/fileTemplates/internal/tests.py.ft new file mode 100644 index 000000000000..2e6fd4c78eed --- /dev/null +++ b/python/edu/course-creator/resources/fileTemplates/internal/tests.py.ft @@ -0,0 +1,17 @@ +from test_helper import run_common_tests, failed, passed, get_task_windows + + +def test_task_windows(): + windows = get_task_windows() + window = windows[0] + if window != "": # TODO: your condition here + passed() + else: + failed() + + +if __name__ == '__main__': + run_common_tests() + test_task_windows() + + diff --git a/python/edu/course-creator/resources/icons/gutter.png b/python/edu/course-creator/resources/icons/gutter.png Binary files differnew file mode 100644 index 000000000000..244e6ca045c5 --- /dev/null +++ b/python/edu/course-creator/resources/icons/gutter.png diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java new file mode 100644 index 000000000000..1eb8690aad22 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java @@ -0,0 +1,80 @@ +package org.jetbrains.plugins.coursecreator; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.event.EditorFactoryEvent; +import com.intellij.openapi.editor.event.EditorFactoryListener; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.coursecreator.format.*; + +public class CCEditorFactoryListener implements EditorFactoryListener { + @Override + public void editorCreated(@NotNull EditorFactoryEvent event) { + Editor editor = event.getEditor(); + Project project = editor.getProject(); + if (project == null) { + return; + } + VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(editor.getDocument()); + if (virtualFile == null) { + return; + } + Course course = CCProjectService.getInstance(project).getCourse(); + if (course == null) { + return; + } + final VirtualFile taskDir = virtualFile.getParent(); + if (taskDir == null || !taskDir.getName().contains("task")) { + return; + } + final VirtualFile lessonDir = taskDir.getParent(); + if (lessonDir == null) return; + final Lesson lesson = course.getLesson(lessonDir.getName()); + final Task task = lesson.getTask(taskDir.getName()); + final TaskFile taskFile = task.getTaskFile(virtualFile.getName()); + TaskFileModificationListener listener = new TaskFileModificationListener(taskFile); + CCProjectService.addDocumentListener(editor.getDocument(), listener); + editor.getDocument().addDocumentListener(listener); + CCProjectService.drawTaskWindows(virtualFile, editor, course); + } + + @Override + public void editorReleased(@NotNull EditorFactoryEvent event) { + Editor editor = event.getEditor(); + Document document = editor.getDocument(); + StudyDocumentListener listener = CCProjectService.getListener(document); + if (listener != null) { + document.removeDocumentListener(listener); + CCProjectService.removeListener(document); + } + editor.getMarkupModel().removeAllHighlighters(); + editor.getSelectionModel().removeSelection(); + } + + private class TaskFileModificationListener extends StudyDocumentListener { + + private final TaskFile myTaskFile; + + public TaskFileModificationListener(TaskFile taskFile) { + super(taskFile); + myTaskFile = taskFile; + } + + @Override + protected void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change) { + int newLength = taskWindow.getReplacementLength() + change; + taskWindow.setReplacementLength(newLength <= 0 ? 0 : newLength); + if (fragment.equals("\n")) { + taskWindow.setReplacementLength(taskWindow.getLength() + 1); + } + } + + @Override + protected boolean needModify() { + return myTaskFile.isTrackChanges(); + } + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java new file mode 100644 index 000000000000..34de943d2ad0 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java @@ -0,0 +1,58 @@ +package org.jetbrains.plugins.coursecreator; + +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.event.EditorFactoryEvent; +import com.intellij.openapi.editor.impl.EditorFactoryImpl; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.coursecreator.format.Course; + +public class CCProjectComponent implements ProjectComponent { + private final Project myProject; + + public CCProjectComponent(Project project) { + myProject = project; + } + + public void initComponent() { + } + + public void disposeComponent() { + } + + @NotNull + public String getComponentName() { + return "CCProjectComponent"; + } + + public void projectOpened() { + StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new Runnable() { + @Override + public void run() { + Course course = CCProjectService.getInstance(myProject).getCourse(); + if (course != null) { + EditorFactory.getInstance().addEditorFactoryListener(new CCEditorFactoryListener(), myProject); + VirtualFile[] files = FileEditorManager.getInstance(myProject).getOpenFiles(); + for (VirtualFile file : files) { + FileEditor fileEditor = FileEditorManager.getInstance(myProject).getSelectedEditor(file); + if (fileEditor instanceof PsiAwareTextEditorImpl) { + Editor editor = ((PsiAwareTextEditorImpl)fileEditor).getEditor(); + new CCEditorFactoryListener().editorCreated(new EditorFactoryEvent(new EditorFactoryImpl(ProjectManager.getInstance()), editor )); + } + } + } + } + }); + } + + public void projectClosed() { + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectGenerator.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectGenerator.java new file mode 100644 index 000000000000..dbaa7265f5c1 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectGenerator.java @@ -0,0 +1,101 @@ +package org.jetbrains.plugins.coursecreator; + +import com.intellij.facet.ui.FacetEditorValidator; +import com.intellij.facet.ui.FacetValidatorsManager; +import com.intellij.facet.ui.ValidationResult; +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.ide.fileTemplates.FileTemplateUtil; +import com.intellij.ide.util.DirectoryUtil; +import com.intellij.openapi.command.WriteCommandAction; +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.platform.DirectoryProjectGenerator; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiManager; +import com.jetbrains.python.newProject.PythonProjectGenerator; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.coursecreator.format.Course; +import org.jetbrains.plugins.coursecreator.ui.CCNewProjectPanel; + +import javax.swing.*; + + +public class CCProjectGenerator extends PythonProjectGenerator implements DirectoryProjectGenerator { + private CCNewProjectPanel mySettingsPanel; + + @Nls + @NotNull + @Override + public String getName() { + return "Course creation"; + } + + @Nullable + @Override + public Object showGenerationSettings(VirtualFile baseDir) throws ProcessCanceledException { + return null; + } + + @Nullable + @Override + public Icon getLogo() { + return null; + } + + + @Override + public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir, + @Nullable Object settings, @NotNull Module module) { + + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = new Course(mySettingsPanel.getName(), mySettingsPanel.getAuthor(), mySettingsPanel.getDescription()); + service.setCourse(course); + + final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(baseDir); + if (projectDir == null) return; + new WriteCommandAction.Simple(project) { + @Override + protected void run() throws Throwable { + final FileTemplate template = FileTemplateManager.getInstance().getInternalTemplate("test_helper"); + try { + FileTemplateUtil.createFromTemplate(template, "test_helper.py", null, projectDir); + } + catch (Exception ignored) { + } + DirectoryUtil.createSubdirectories("hints", projectDir, "\\/"); + } + }.execute(); + + } + + @NotNull + @Override + public ValidationResult validate(@NotNull String s) { + String message = ""; + message = mySettingsPanel.getDescription().equals("") ? "Enter description" : message; + message = mySettingsPanel.getAuthor().equals("") ? "Enter author name" : message; + message = mySettingsPanel.getName().equals("") ? "Enter course name" : message; + return message.equals("")? ValidationResult.OK : new ValidationResult(message) ; + } + + @Nullable + @Override + public JPanel extendBasePanel() throws ProcessCanceledException { + mySettingsPanel = new CCNewProjectPanel(); + mySettingsPanel.registerValidators(new FacetValidatorsManager() { + public void registerValidator(FacetEditorValidator validator, JComponent... componentsToWatch) { + throw new UnsupportedOperationException(); + } + + public void validate() { + fireStateChanged(); + } + }); + return mySettingsPanel.getMainPanel(); + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java new file mode 100644 index 000000000000..1e38bab865cb --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java @@ -0,0 +1,138 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.coursecreator; + +import com.intellij.ide.projectView.ProjectView; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.util.xmlb.XmlSerializer; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.coursecreator.format.*; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@State(name = "CCProjectService", + storages = { + @Storage(file = "$PROJECT_CONFIG_DIR$/course_service.xml") + } +) +public class CCProjectService implements PersistentStateComponent<Element> { + + private static final Logger LOG = Logger.getInstance(CCProjectService.class.getName()); + public Course myCourse; + public static final String COURSE_ELEMENT = "course"; + private static final Map<Document, StudyDocumentListener> myDocumentListeners = new HashMap<Document, StudyDocumentListener>(); + + public void setCourse(@NotNull final Course course) { + myCourse = course; + } + + public Course getCourse() { + return myCourse; + } + + @Override + public Element getState() { + final Element el = new Element("CCProjectService"); + 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); + } + + public static CCProjectService getInstance(@NotNull Project project) { + return ServiceManager.getService(project, CCProjectService.class); + } + + public static void deleteProjectFile(File file, @NotNull final Project project) { + if (!file.delete()) { + LOG.info("Failed to delete file " + file.getPath()); + } + VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + ProjectView.getInstance(project).refresh(); + } + + public static void drawTaskWindows(@NotNull final VirtualFile virtualFile, @NotNull final Editor editor, @NotNull final Course course) { + VirtualFile taskDir = virtualFile.getParent(); + if (taskDir == null) { + return; + } + String taskDirName = taskDir.getName(); + if (!taskDirName.contains("task")) { + return; + } + VirtualFile lessonDir = taskDir.getParent(); + if (lessonDir == null) { + return; + } + String lessonDirName = lessonDir.getName(); + if (!lessonDirName.contains("lesson")) { + return; + } + Lesson lesson = course.getLessonsMap().get(lessonDirName); + if (lesson == null) { + return; + } + Task task = lesson.getTask(taskDirName); + if (task == null) { + return; + } + TaskFile taskFile = task.getTaskFile(virtualFile.getName()); + if (taskFile == null) { + return; + } + List<TaskWindow> taskWindows = taskFile.getTaskWindows(); + for (TaskWindow taskWindow : taskWindows) { + taskWindow.drawHighlighter(editor); + } + } + + public static void addDocumentListener(Document document, StudyDocumentListener listener) { + myDocumentListeners.put(document, listener); + } + + public static StudyDocumentListener getListener(Document document) { + return myDocumentListeners.get(document); + } + + public static void removeListener(Document document) { + myDocumentListeners.remove(document); + } + + public static boolean indexIsValid(int index, List<TaskWindow> collection) { + int size = collection.size(); + return index >= 0 && index < size; + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java new file mode 100644 index 000000000000..d803e0e8fd97 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java @@ -0,0 +1,71 @@ +package org.jetbrains.plugins.coursecreator; + +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 org.jetbrains.plugins.coursecreator.format.TaskFile; +import org.jetbrains.plugins.coursecreator.format.TaskWindow; + +/** + * author: liana + * data: 7/16/14. + * Listens changes in study files and updates + * coordinates of all the windows in current task file + */ +public abstract 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) { + if (!needModify()) { + return; + } + DocumentEventImpl event = (DocumentEventImpl)e; + Document document = e.getDocument(); + int offset = e.getOffset(); + int change = event.getNewLength() - event.getOldLength(); + if (myTaskWindow != null) { + updateTaskWindowLength(e.getNewFragment(), myTaskWindow, change); + } + 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); + } + } + + protected abstract void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change); + + protected abstract boolean needModify(); +} + diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java new file mode 100644 index 000000000000..ff88cea5fd42 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java @@ -0,0 +1,105 @@ +package org.jetbrains.plugins.coursecreator.actions; + +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.*; +import org.jetbrains.plugins.coursecreator.ui.CreateTaskWindowDialog; + +public class AddTaskWindow extends DumbAwareAction { + public AddTaskWindow() { + super("Add task window","Add task window", null); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = e.getData(CommonDataKeys.PROJECT); + if (project == null) { + return; + } + final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); + if (file == null) return; + final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext()); + if (editor == null) return; + + final SelectionModel model = editor.getSelectionModel(); + final Document document = PsiDocumentManager.getInstance(project).getDocument(file); + if (document == null) return; + final int start = model.getSelectionStart(); + final int end = model.getSelectionEnd(); + final int lineNumber = document.getLineNumber(start); + final int length = end - start; + int realStart = start - document.getLineStartOffset(lineNumber); + + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + final PsiDirectory taskDir = file.getContainingDirectory(); + final PsiDirectory lessonDir = taskDir.getParent(); + if (lessonDir == null) return; + + final Lesson lesson = course.getLesson(lessonDir.getName()); + final Task task = lesson.getTask(taskDir.getName()); + final TaskFile taskFile = task.getTaskFile(file.getName()); + final TaskWindow taskWindow = new TaskWindow(lineNumber, realStart, length, model.getSelectedText()); + CreateTaskWindowDialog dlg = new CreateTaskWindowDialog(project, taskWindow, lesson.getIndex(), task.getIndex(), file.getVirtualFile().getNameWithoutExtension(), taskFile.getTaskWindows().size() + 1); + dlg.show(); + if (dlg.getExitCode() != DialogWrapper.OK_EXIT_CODE) { + return; + } + int index = taskFile.getTaskWindows().size() + 1; + taskFile.addTaskWindow(taskWindow, index); + taskWindow.drawHighlighter(editor); + DaemonCodeAnalyzerImpl.getInstance(project).restart(file); + } + + @Override + public void update(AnActionEvent event) { + final Presentation presentation = event.getPresentation(); + final Project project = event.getData(CommonDataKeys.PROJECT); + if (project == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + final Editor editor = CommonDataKeys.EDITOR.getData(event.getDataContext()); + final PsiFile file = CommonDataKeys.PSI_FILE.getData(event.getDataContext()); + if (editor == null || file == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + if (!editor.getSelectionModel().hasSelection()) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + final PsiDirectory taskDir = file.getContainingDirectory(); + final PsiDirectory lessonDir = taskDir.getParent(); + if (lessonDir == null) return; + + final Lesson lesson = course.getLesson(lessonDir.getName()); + final Task task = lesson.getTask(taskDir.getName()); + if (task == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + presentation.setVisible(true); + presentation.setEnabled(true); + + } +}
\ No newline at end of file diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java new file mode 100644 index 000000000000..05428f4e82d1 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java @@ -0,0 +1,198 @@ +package org.jetbrains.plugins.coursecreator.actions; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.intellij.icons.AllIcons; +import com.intellij.ide.projectView.ProjectView; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.util.io.ZipUtil; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.StudyDocumentListener; +import org.jetbrains.plugins.coursecreator.format.*; +import org.jetbrains.plugins.coursecreator.ui.CreateCourseArchiveDialog; + +import java.io.*; +import java.util.*; +import java.util.zip.ZipOutputStream; + +public class CreateCourseArchive extends DumbAwareAction { + private static final Logger LOG = Logger.getInstance(CreateCourseArchive.class.getName()); + String myZipName; + String myLocationDir; + + public void setZipName(String zipName) { + myZipName = zipName; + } + + public void setLocationDir(String locationDir) { + myLocationDir = locationDir; + } + + public CreateCourseArchive() { + super("Generate course archive", "Generate course archive", AllIcons.FileTypes.Archive); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = e.getData(CommonDataKeys.PROJECT); + if (project == null) { + return; + } + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + if (course == null) return; + CreateCourseArchiveDialog dlg = new CreateCourseArchiveDialog(project, this); + dlg.show(); + if (dlg.getExitCode() != DialogWrapper.OK_EXIT_CODE) { + return; + } + final VirtualFile baseDir = project.getBaseDir(); + final Map<String, Lesson> lessons = course.getLessonsMap(); + //List<FileEditor> editorList = new ArrayList<FileEditor>(); + Map<VirtualFile, TaskFile> taskFiles = new HashMap<VirtualFile, TaskFile>(); + for (Map.Entry<String, Lesson> lesson : lessons.entrySet()) { + final VirtualFile lessonDir = baseDir.findChild(lesson.getKey()); + if (lessonDir == null) continue; + for (Map.Entry<String, Task> task : lesson.getValue().myTasksMap.entrySet()) { + final VirtualFile taskDir = lessonDir.findChild(task.getKey()); + if (taskDir == null) continue; + for (Map.Entry<String, TaskFile> entry : task.getValue().task_files.entrySet()) { + final VirtualFile file = taskDir.findChild(entry.getKey()); + if (file == null) continue; + final Document document = FileDocumentManager.getInstance().getDocument(file); + if (document == null) continue; + final TaskFile taskFile = entry.getValue(); + document.addDocumentListener(new InsertionListener(taskFile)); + taskFiles.put(file, taskFile); + taskFile.setTrackChanges(false); + Collections.sort(taskFile.getTaskWindows()); + for (int i = taskFile.getTaskWindows().size() - 1; i >=0 ; i--) { + final TaskWindow taskWindow = taskFile.getTaskWindows().get(i); + final String taskText = taskWindow.getTaskText(); + final int lineStartOffset = document.getLineStartOffset(taskWindow.line); + final int offset = lineStartOffset + taskWindow.start; + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + document.replaceString(offset, offset + taskWindow.getReplacementLength(), taskText); + FileDocumentManager.getInstance().saveDocument(document); + } + }); + } + }, "x", "qwe"); + } + } + } + } + generateJson(project); + try { + File zipFile = new File(myLocationDir, myZipName + ".zip"); + ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile))); + + for (Map.Entry<String, Lesson> entry : lessons.entrySet()) { + final VirtualFile lessonDir = baseDir.findChild(entry.getKey()); + if (lessonDir == null) continue; + + ZipUtil.addFileOrDirRecursively(zos, null, new File(lessonDir.getPath()), lessonDir.getName(), null, null); + } + ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "hints"), "hints", null, null); + ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "course.json"), "course.json", null, null); + ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "test_helper.py"), "test_helper.py", null, null); + zos.close(); + Messages.showInfoMessage("Course archive was saved to " + zipFile.getPath(), "Course Archive Was Created Successfully"); + } + catch (IOException e1) { + LOG.error(e1); + } + + for (Map.Entry<VirtualFile, TaskFile> entry: taskFiles.entrySet()) { + TaskFile value = entry.getValue(); + final Document document = FileDocumentManager.getInstance().getDocument(entry.getKey()); + if (document == null) { + continue; + } + for (final TaskWindow taskWindow : value.getTaskWindows()){ + final int lineStartOffset = document.getLineStartOffset(taskWindow.line); + final int offset = lineStartOffset + taskWindow.start; + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + document.replaceString(offset, offset + taskWindow.length, taskWindow.getPossibleAnswer()); + FileDocumentManager.getInstance().saveDocument(document); + } + }); + } + }, "x", "qwe"); + } + value.setTrackChanges(true); + } + VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + ProjectView.getInstance(project).refresh(); + } + + private void generateJson(Project project) { + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); + final String json = gson.toJson(course); + final File courseJson = new File(project.getBasePath(), "course.json"); + FileWriter writer = null; + try { + writer = new FileWriter(courseJson); + writer.write(json); + } + catch (IOException e) { + Messages.showErrorDialog(e.getMessage(), "Failed to Generate Json"); + LOG.info(e); + } + catch (Exception e) { + Messages.showErrorDialog(e.getMessage(), "Failed to Generate Json"); + LOG.info(e); + } + finally { + try { + if (writer != null) { + writer.close(); + } + } + catch (IOException e1) { + //close silently + } + } + } + + private class InsertionListener extends StudyDocumentListener { + + public InsertionListener(TaskFile taskFile) { + super(taskFile); + } + + @Override + protected void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change) { + //we don't need to update task window length + } + + @Override + protected boolean needModify() { + return true; + } + } +}
\ No newline at end of file diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateLesson.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateLesson.java new file mode 100644 index 000000000000..15d9f8367db9 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateLesson.java @@ -0,0 +1,89 @@ +package org.jetbrains.plugins.coursecreator.actions; + +import com.intellij.ide.IdeView; +import com.intellij.ide.util.DirectoryChooserUtil; +import com.intellij.ide.util.DirectoryUtil; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.psi.PsiDirectory; +import com.intellij.util.PlatformIcons; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.Course; +import org.jetbrains.plugins.coursecreator.format.Lesson; + +public class CreateLesson extends DumbAwareAction { + public CreateLesson() { + super("Lesson", "Create new Lesson", PlatformIcons.DIRECTORY_CLOSED_ICON); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final IdeView view = e.getData(LangDataKeys.IDE_VIEW); + final Project project = e.getData(CommonDataKeys.PROJECT); + + if (view == null || project == null) { + return; + } + final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view); + if (directory == null) return; + + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + final int size = course.getLessons().size(); + final String lessonName = Messages.showInputDialog("Name:", "Lesson Name", null, "lesson" + (size+1), null); + if (lessonName == null) return; + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final PsiDirectory lessonDirectory = DirectoryUtil.createSubdirectories("lesson" + (size+1), directory, "\\/"); + if (lessonDirectory != null) { + view.selectElement(lessonDirectory); + final Lesson lesson = new Lesson(lessonName); + lesson.setIndex(size + 1); + course.addLesson(lesson, lessonDirectory); + } + } + }); + } + + @Override + public void update(AnActionEvent event) { + final Presentation presentation = event.getPresentation(); + final Project project = event.getData(CommonDataKeys.PROJECT); + if (project == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + + final IdeView view = event.getData(LangDataKeys.IDE_VIEW); + if (view == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + + final PsiDirectory[] directories = view.getDirectories(); + if (directories.length == 0) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view); + if (directory != null && !project.getBaseDir().equals(directory.getVirtualFile())) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + presentation.setVisible(true); + presentation.setEnabled(true); + + } +}
\ No newline at end of file diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java new file mode 100644 index 000000000000..0940135b97be --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java @@ -0,0 +1,122 @@ +package org.jetbrains.plugins.coursecreator.actions; + +import com.intellij.ide.IdeView; +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.ide.fileTemplates.FileTemplateUtil; +import com.intellij.ide.util.DirectoryChooserUtil; +import com.intellij.ide.util.DirectoryUtil; +import com.intellij.ide.util.EditorHelper; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.util.PlatformIcons; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.Course; +import org.jetbrains.plugins.coursecreator.format.Lesson; +import org.jetbrains.plugins.coursecreator.format.Task; + +public class CreateTask extends DumbAwareAction { + public CreateTask() { + super("Task", "Create new Task", PlatformIcons.DIRECTORY_CLOSED_ICON); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final IdeView view = e.getData(LangDataKeys.IDE_VIEW); + final Project project = e.getData(CommonDataKeys.PROJECT); + + if (view == null || project == null) { + return; + } + final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view); + + if (directory == null) return; + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + final Lesson lesson = course.getLesson(directory.getName()); + final int size = lesson.getTasklist().size(); + + final String taskName = Messages.showInputDialog("Name:", "Task Name", null, "task" + (size + 1), null); + if (taskName == null) return; + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final PsiDirectory taskDirectory = DirectoryUtil.createSubdirectories("task" + (size + 1), directory, "\\/"); + if (taskDirectory != null) { + final FileTemplate template = FileTemplateManager.getInstance().getInternalTemplate("task.html"); + final FileTemplate testsTemplate = FileTemplateManager.getInstance().getInternalTemplate("tests"); + final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.py"); + try { + final PsiElement taskFile = FileTemplateUtil.createFromTemplate(template, "task.html", null, taskDirectory); + final PsiElement testsFile = FileTemplateUtil.createFromTemplate(testsTemplate, "tests.py", null, taskDirectory); + final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, "file1" + ".py", null, taskDirectory); + + final Task task = new Task(taskName); + task.addTaskFile(taskPyFile.getContainingFile().getName(), size + 1); + task.setIndex(size + 1); + lesson.addTask(task, taskDirectory); + + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + EditorHelper.openInEditor(testsFile, false); + EditorHelper.openInEditor(taskPyFile, false); + view.selectElement(taskFile); + } + }); + } + catch (Exception ignored) { + } + + + } + } + }); + } + + @Override + public void update(AnActionEvent event) { + final Presentation presentation = event.getPresentation(); + final Project project = event.getData(CommonDataKeys.PROJECT); + if (project == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + + final IdeView view = event.getData(LangDataKeys.IDE_VIEW); + if (view == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + + final PsiDirectory[] directories = view.getDirectories(); + if (directories.length == 0) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view); + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + if (course != null && directory != null && course.getLesson(directory.getName()) == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + + presentation.setVisible(true); + presentation.setEnabled(true); + + } +}
\ No newline at end of file diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java new file mode 100644 index 000000000000..5aafcebefd6e --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java @@ -0,0 +1,110 @@ +package org.jetbrains.plugins.coursecreator.actions; + +import com.intellij.ide.IdeView; +import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ide.fileTemplates.FileTemplateManager; +import com.intellij.ide.fileTemplates.FileTemplateUtil; +import com.intellij.ide.util.DirectoryChooserUtil; +import com.intellij.ide.util.EditorHelper; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import icons.PythonPsiApiIcons; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.Course; +import org.jetbrains.plugins.coursecreator.format.Lesson; +import org.jetbrains.plugins.coursecreator.format.Task; + +public class CreateTaskFile extends DumbAwareAction { + + public CreateTaskFile() { + super("Task File", "Create new Task File", PythonPsiApiIcons.PythonFile); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final IdeView view = e.getData(LangDataKeys.IDE_VIEW); + final Project project = e.getData(CommonDataKeys.PROJECT); + + if (view == null || project == null) { + return; + } + final PsiDirectory taskDir = DirectoryChooserUtil.getOrChooseDirectory(view); + if (taskDir == null) return; + PsiDirectory lessonDir = taskDir.getParent(); + if (lessonDir == null) { + return; + } + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + final Lesson lesson = course.getLesson(lessonDir.getName()); + final Task task = lesson.getTask(taskDir.getName()); + + final int index = task.getTaskFiles().size() + 1; + String generatedName = "file" + index; + final String taskFileName = Messages.showInputDialog("Name:", "Task File Name", null, generatedName, null); + if (taskFileName == null) return; + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.py"); + try { + final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, taskFileName + ".py", null, taskDir); + task.addTaskFile(taskPyFile.getContainingFile().getName(), index); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + EditorHelper.openInEditor(taskPyFile, false); + view.selectElement(taskPyFile); + } + }); + } + catch (Exception ignored) { + } + } + }); + } + + @Override + public void update(AnActionEvent event) { + final Presentation presentation = event.getPresentation(); + final Project project = event.getData(CommonDataKeys.PROJECT); + if (project == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + + final IdeView view = event.getData(LangDataKeys.IDE_VIEW); + if (view == null) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + + final PsiDirectory[] directories = view.getDirectories(); + if (directories.length == 0) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view); + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + if (course != null && directory != null && !directory.getName().contains("task")) { + presentation.setVisible(false); + presentation.setEnabled(false); + return; + } + presentation.setVisible(true); + presentation.setEnabled(true); + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/DeleteTaskWindow.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/DeleteTaskWindow.java new file mode 100644 index 000000000000..2724759a8c4f --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/DeleteTaskWindow.java @@ -0,0 +1,62 @@ +package org.jetbrains.plugins.coursecreator.actions; + +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.*; + +import java.util.List; + +@SuppressWarnings("ComponentNotRegistered") +public class DeleteTaskWindow extends DumbAwareAction { + @NotNull + private final TaskWindow myTaskWindow; + + public DeleteTaskWindow(@NotNull final TaskWindow taskWindow) { + super("Delete task window","Delete task window", null); + myTaskWindow = taskWindow; + } + + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = e.getData(PlatformDataKeys.PROJECT); + if (project == null) return; + final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); + if (file == null) return; + final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext()); + if (editor == null) { + return; + } + final Document document = PsiDocumentManager.getInstance(project).getDocument(file); + if (document == null) return; + + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + final PsiDirectory taskDir = file.getContainingDirectory(); + final PsiDirectory lessonDir = taskDir.getParent(); + if (lessonDir == null) return; + + final Lesson lesson = course.getLesson(lessonDir.getName()); + final Task task = lesson.getTask(taskDir.getName()); + final TaskFile taskFile = task.getTaskFile(file.getName()); + final List<TaskWindow> taskWindows = taskFile.getTaskWindows(); + if (taskWindows.contains(myTaskWindow)) { + myTaskWindow.removeResources(project); + taskWindows.remove(myTaskWindow); + editor.getMarkupModel().removeAllHighlighters(); + CCProjectService.drawTaskWindows(file.getVirtualFile(), editor, course); + DaemonCodeAnalyzerImpl.getInstance(project).restart(file); + } + } + +}
\ No newline at end of file diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/ShowTaskWindowText.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/ShowTaskWindowText.java new file mode 100644 index 000000000000..7c7e7fa727b8 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/ShowTaskWindowText.java @@ -0,0 +1,44 @@ +package org.jetbrains.plugins.coursecreator.actions; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.*; +import org.jetbrains.plugins.coursecreator.ui.CreateTaskWindowDialog; + +@SuppressWarnings("ComponentNotRegistered") +public class ShowTaskWindowText extends DumbAwareAction { + @NotNull + private final TaskWindow myTaskWindow; + + public ShowTaskWindowText(@NotNull final TaskWindow taskWindow) { + super("Add task window","Add task window", null); + myTaskWindow = taskWindow; + } + + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = e.getData(PlatformDataKeys.PROJECT); + if (project == null) return; + final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext()); + if (file == null) return; + final CCProjectService service = CCProjectService.getInstance(project); + final Course course = service.getCourse(); + final PsiDirectory taskDir = file.getContainingDirectory(); + final PsiDirectory lessonDir = taskDir.getParent(); + if (lessonDir == null) return; + + final Lesson lesson = course.getLesson(lessonDir.getName()); + final Task task = lesson.getTask(taskDir.getName()); + final TaskFile taskFile = task.getTaskFile(file.getName()); + //TODO: copy task window and return if modification canceled + CreateTaskWindowDialog dlg = new CreateTaskWindowDialog(project, myTaskWindow, lesson.getIndex(), task.getIndex(), file.getVirtualFile().getNameWithoutExtension(), taskFile.getTaskWindows().size() + 1); + dlg.show(); + } +}
\ No newline at end of file diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java new file mode 100644 index 000000000000..eb62d59cd9b1 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java @@ -0,0 +1,55 @@ +package org.jetbrains.plugins.coursecreator.format; + +import com.google.gson.annotations.Expose; +import com.intellij.psi.PsiDirectory; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Course { + @Expose public List<Lesson> lessons = new ArrayList<Lesson>(); + @Expose public String description; + + @Expose public String name; + @Expose public String author; + + public Map<String, Lesson> myLessonsMap = new HashMap<String, Lesson>(); + + public Map<String, Lesson> getLessonsMap() { + return myLessonsMap; + } + + public Lesson getLesson(@NotNull final String name) { + return myLessonsMap.get(name); + } + + + public Course() { + } + + public Course(@NotNull final String name, @NotNull final String author, @NotNull final String description) { + this.description = description; + this.name = name; + this.author = author; + } + + public List<Lesson> getLessons() { + return lessons; + } + + public void addLesson(@NotNull final Lesson lesson, @NotNull final PsiDirectory directory) { + lessons.add(lesson); + myLessonsMap.put(directory.getName(), lesson); + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java new file mode 100644 index 000000000000..38720140caf1 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java @@ -0,0 +1,45 @@ +package org.jetbrains.plugins.coursecreator.format; + +import com.google.gson.annotations.Expose; +import com.intellij.psi.PsiDirectory; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Lesson { + @Expose public String name; + @Expose public List<Task> task_list = new ArrayList<Task>(); + + public int myIndex; + public Map<String, Task> myTasksMap = new HashMap<String, Task>(); + + public Lesson() {} + + public Lesson(@NotNull final String name) { + this.name = name; + } + + public void addTask(@NotNull final Task task, PsiDirectory taskDirectory) { + myTasksMap.put(taskDirectory.getName(), task); + task_list.add(task); + } + + public Task getTask(@NotNull final String name) { + return myTasksMap.get(name); + } + + public List<Task> getTasklist() { + return task_list; + } + + public void setIndex(int index) { + myIndex = index; + } + + public int getIndex() { + return myIndex; + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java new file mode 100644 index 000000000000..e6c085b5d6a1 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java @@ -0,0 +1,41 @@ +package org.jetbrains.plugins.coursecreator.format; + +import com.google.gson.annotations.Expose; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +public class Task { + @Expose public String name; + @Expose public Map<String, TaskFile> task_files = new HashMap<String, TaskFile>(); + public int myIndex; + + public Task() {} + + public Task(@NotNull final String name) { + this.name = name; + } + + public int getIndex() { + return myIndex; + } + + public void addTaskFile(@NotNull final String name, int index) { + TaskFile taskFile = new TaskFile(); + taskFile.setIndex(index); + task_files.put(name, taskFile); + } + + public TaskFile getTaskFile(@NotNull final String name) { + return task_files.get(name); + } + + public void setIndex(int index) { + myIndex = index; + } + + public Map<String, TaskFile> getTaskFiles() { + return task_files; + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java new file mode 100644 index 000000000000..85f0d91983f2 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java @@ -0,0 +1,111 @@ +package org.jetbrains.plugins.coursecreator.format; + +import com.google.gson.annotations.Expose; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.LogicalPosition; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.coursecreator.CCProjectService; + +import java.util.ArrayList; +import java.util.List; + +public class TaskFile { + @Expose public List<TaskWindow> task_windows = new ArrayList<TaskWindow>(); + public int myIndex; + public boolean myTrackChanges = true; + + public boolean isTrackChanges() { + return myTrackChanges; + } + + public void setTrackChanges(boolean trackChanges) { + myTrackChanges = trackChanges; + } + + public TaskFile() {} + + public void addTaskWindow(@NotNull final TaskWindow taskWindow, int index) { + taskWindow.setIndex(index); + task_windows.add(taskWindow); + } + + public List<TaskWindow> getTaskWindows() { + return task_windows; + } + + public void setIndex(int index) { + myIndex = index; + } + + + /** + * @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 : task_windows) { + if (tw.getLine() <= line) { + int twStartOffset = tw.getRealStartOffset(document); + final int length = tw.getReplacementLength() > 0 ? tw.getReplacementLength() : 0; + int twEndOffset = twStartOffset + length; + 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 : task_windows) { + if (taskTaskWindow.getLine() >= startLine) { + taskTaskWindow.setLine(taskTaskWindow.getLine() + change); + } + } + } + + /** + * 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 : task_windows) { + if ((w.getLine() == line) && (w.getStart() >= oldEndOffsetInLine)) { + int distance = w.getStart() - oldEndOffsetInLine; + boolean coveredByPrevTW = false; + int prevIndex = w.getIndex() - 1; + if (CCProjectService.indexIsValid(prevIndex, task_windows)) { + TaskWindow prevTW = task_windows.get(prevIndex); + if (prevTW.getLine() == line) { + int endOffset = prevTW.getStart() + prevTW.getLength(); + if (endOffset >= newEndOffsetInLine) { + coveredByPrevTW = true; + } + } + } + if (lineChange != 0 || newEndOffsetInLine <= w.getStart() || coveredByPrevTW) { + w.setStart(distance + newEndOffsetInLine); + w.setLine(line + lineChange); + } + } + } + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java new file mode 100644 index 000000000000..cb6418ec75d7 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java @@ -0,0 +1,133 @@ +package org.jetbrains.plugins.coursecreator.format; + +import com.google.gson.annotations.Expose; +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.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.coursecreator.CCProjectService; + +import java.io.File; + +public class TaskWindow implements Comparable{ + + @Expose public int line; + @Expose public int start; + @Expose public String hint; + @Expose public String possible_answer; + @Expose public int length; + public String myTaskText; + public int myReplacementLength; + public int myIndex; + + public TaskWindow() {} + + public TaskWindow(int line, int start, int length, String selectedText) { + this.line = line; + this.start = start; + myReplacementLength = length; + this.possible_answer = selectedText; + } + + public void setTaskText(@NotNull final String taskText) { + myTaskText = taskText; + length = myTaskText.length(); + } + + public String getTaskText() { + return myTaskText; + } + + public int getReplacementLength() { + return myReplacementLength; + } + + public void setHint(String hint) { + this.hint = hint; + } + + public String getHintName() { + return hint; + } + + public void removeResources(@NotNull final Project project) { + if (hint != null) { + VirtualFile hints = project.getBaseDir().findChild("hints"); + if (hints == null) { + return; + } + File hintFile = new File(hints.getPath(), hint); + CCProjectService.deleteProjectFile(hintFile, project); + } + } + + public void drawHighlighter(@NotNull final Editor editor) { + int startOffset = editor.getDocument().getLineStartOffset(line) + start; + int endOffset = startOffset + myReplacementLength; + TextAttributes defaultTestAttributes = + EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.LIVE_TEMPLATE_ATTRIBUTES); + RangeHighlighter highlighter = + editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, HighlighterLayer.LAST + 1, defaultTestAttributes, + HighlighterTargetArea.EXACT_RANGE); + highlighter.setGreedyToLeft(true); + highlighter.setGreedyToRight(true); + } + + public int getIndex() { + return myIndex; + } + + public void setIndex(int index) { + + myIndex = index; + } + + public void setReplacementLength(int replacementLength) { + myReplacementLength = replacementLength; + } + + public int getLine() { + return line; + } + + public int getRealStartOffset(Document document) { + return document.getLineStartOffset(line) + start; + } + + public void setLine(int line) { + this.line = line; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + @Override + public int compareTo(Object o) { + TaskWindow taskWindow = (TaskWindow)o; + int lineDiff = line - taskWindow.line; + if (lineDiff == 0) { + return start - taskWindow.start; + } + return lineDiff; + } + + public String getPossibleAnswer() { + return possible_answer; + } + + public int getLength() { + return length; + } +}
\ No newline at end of file diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/CCTaskLineMarkerProvider.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/CCTaskLineMarkerProvider.java new file mode 100644 index 000000000000..1818b658ddfc --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/CCTaskLineMarkerProvider.java @@ -0,0 +1,75 @@ +package org.jetbrains.plugins.coursecreator.highlighting; + +import com.intellij.codeHighlighting.Pass; +import com.intellij.codeInsight.daemon.LineMarkerInfo; +import com.intellij.codeInsight.daemon.LineMarkerProvider; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.markup.GutterIconRenderer; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.*; + +import java.util.Collection; +import java.util.List; + +public class CCTaskLineMarkerProvider implements LineMarkerProvider { + private static final Logger LOG = Logger.getInstance(CCTaskLineMarkerProvider.class.getName()); + + @Nullable + @Override + public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) { + return null; + } + + @Override + public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull final Collection<LineMarkerInfo> result) { + for (PsiElement element : elements) { + if (element instanceof PsiFile) { + final Project project = element.getProject(); + final Course course = CCProjectService.getInstance(project).getCourse(); + if (course == null) return; + final String taskFileName = ((PsiFile) element).getName(); + final PsiDirectory taskDir = ((PsiFile) element).getParent(); + if (taskDir == null) continue; + final String taskDirName = taskDir.getName(); + final PsiDirectory lessonDir = taskDir.getParentDirectory(); + if (lessonDir == null) continue; + final String lessonDirName = lessonDir.getName(); + final Lesson lesson = course.getLesson(lessonDirName); + if (lesson == null) continue; + final Task task = lesson.getTask(taskDirName); + final TaskFile taskFile = task.getTaskFile(taskFileName); + if (taskFile == null) continue; + final Document document = PsiDocumentManager.getInstance(project).getDocument((PsiFile) element); + if (document == null) continue; + for (final TaskWindow taskWindow : taskFile.getTaskWindows()) { + if (taskWindow.line > document.getLineCount()) continue; + final int lineStartOffset = document.getLineStartOffset(taskWindow.line); + final int offset = lineStartOffset + taskWindow.start; + if (offset > document.getTextLength()) continue; + final TextRange textRange = TextRange.create(offset, offset + taskWindow.getReplacementLength()); + @SuppressWarnings("unchecked") + final LineMarkerInfo info = new LineMarkerInfo(element, textRange, + IconLoader.getIcon("/icons/gutter.png"), Pass.UPDATE_OVERRIDEN_MARKERS, + null, null, GutterIconRenderer.Alignment.CENTER) { + @Nullable + @Override + public GutterIconRenderer createGutterRenderer() { + return new TaskTextGutter(taskWindow, this); + } + }; + result.add(info); + } + } + } + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/TaskTextGutter.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/TaskTextGutter.java new file mode 100644 index 000000000000..80b267485192 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/highlighting/TaskTextGutter.java @@ -0,0 +1,60 @@ +package org.jetbrains.plugins.coursecreator.highlighting; + +import com.intellij.codeInsight.daemon.LineMarkerInfo; +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.util.IconLoader; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.coursecreator.actions.DeleteTaskWindow; +import org.jetbrains.plugins.coursecreator.actions.ShowTaskWindowText; +import org.jetbrains.plugins.coursecreator.format.TaskWindow; + +import javax.swing.*; + +public class TaskTextGutter extends LineMarkerInfo.LineMarkerGutterIconRenderer { + @NotNull + private final TaskWindow myTaskWindow; + + public TaskTextGutter(@NotNull final TaskWindow taskWindow, LineMarkerInfo lineMarkerInfo) { + super(lineMarkerInfo); + myTaskWindow = taskWindow; + } + + @NotNull + @Override + public Icon getIcon() { + return IconLoader.getIcon("/icons/gutter.png"); + } + + @Override + public boolean equals(Object o) { + return this == o || o instanceof TaskTextGutter + && myTaskWindow.getTaskText().equals(((TaskTextGutter) o).getTaskWindow().getTaskText()); + } + + @NotNull + public TaskWindow getTaskWindow() { + return myTaskWindow; + } + + @Override + public int hashCode() { + return myTaskWindow.hashCode(); + } + + @Nullable + @Override + public AnAction getClickAction() { + return new ShowTaskWindowText(myTaskWindow); + } + + @Nullable + @Override + public ActionGroup getPopupMenuActions() { + DefaultActionGroup group = new DefaultActionGroup(); + group.add(new DeleteTaskWindow(myTaskWindow)); + return group; + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java new file mode 100644 index 000000000000..1a7304123f0a --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java @@ -0,0 +1,63 @@ +package org.jetbrains.plugins.coursecreator.projectView; + +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import com.intellij.ui.SimpleTextAttributes; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.Course; +import org.jetbrains.plugins.coursecreator.format.Lesson; +import org.jetbrains.plugins.coursecreator.format.Task; + +public class CCDirectoryNode extends PsiDirectoryNode { + private final PsiDirectory myValue; + private final Project myProject; + + public CCDirectoryNode(@NotNull final Project project, + PsiDirectory value, + ViewSettings viewSettings) { + super(project, value, viewSettings); + myValue = value; + myProject = project; + } + + @Override + protected void updateImpl(PresentationData data) { + String valueName = myValue.getName(); + final Course course = CCProjectService.getInstance(myProject).getCourse(); + if (course == null) return; + if (myProject.getBaseDir().equals(myValue.getVirtualFile())) { + data.clearText(); + data.addText(valueName, SimpleTextAttributes.REGULAR_ATTRIBUTES); + data.addText(" (" + course.getName() + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); + return; + } + final Lesson lesson = course.getLesson(valueName); + if (lesson != null) { + data.clearText(); + data.addText(valueName, SimpleTextAttributes.REGULAR_ATTRIBUTES); + data.addText(" (" + lesson.name + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); + return; + } + else { + final PsiDirectory parentDir = myValue.getParentDirectory(); + if (parentDir != null) { + final Lesson parentLesson = course.getLesson(parentDir.getName()); + if (parentLesson != null) { + final Task task = parentLesson.getTask(valueName); + if (task != null) { + data.clearText(); + data.addText(valueName, SimpleTextAttributes.REGULAR_ATTRIBUTES); + data.addText(" (" + task.name + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); + return; + } + } + } + } + data.setPresentableText(valueName); + } + +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java new file mode 100644 index 000000000000..69b78ec9a92b --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java @@ -0,0 +1,55 @@ +package org.jetbrains.plugins.coursecreator.projectView; + +import com.intellij.ide.projectView.TreeStructureProvider; +import com.intellij.ide.projectView.ViewSettings; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDirectory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.coursecreator.CCProjectService; + +import java.util.ArrayList; +import java.util.Collection; + +public class CCTreeStructureProvider implements TreeStructureProvider, DumbAware { + @NotNull + @Override + public Collection<AbstractTreeNode> modify(@NotNull AbstractTreeNode parent, + @NotNull Collection<AbstractTreeNode> children, + ViewSettings settings) { + if (!needModify(parent)) { + return children; + } + Collection<AbstractTreeNode> nodes = new ArrayList<AbstractTreeNode>(); + for (AbstractTreeNode node : children) { + Project project = node.getProject(); + if (project != null) { + if (node.getValue() instanceof PsiDirectory) { + PsiDirectory directory = (PsiDirectory) node.getValue(); + nodes.add(new CCDirectoryNode(project, directory, settings)); + } else { + nodes.add(node); + } + } + } + return nodes; + } + + private static boolean needModify(@NotNull final AbstractTreeNode parent) { + Project project = parent.getProject(); + if (project != null) { + if (CCProjectService.getInstance(project).getCourse() == null) { + return false; + } + } + return true; + } + + @Nullable + @Override + public Object getData(Collection<AbstractTreeNode> selected, String dataName) { + return null; + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.form b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.form new file mode 100644 index 000000000000..71e27857a2ee --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.form @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.plugins.coursecreator.ui.CCNewProjectPanel"> + <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <xy x="20" y="20" width="500" height="400"/> + </constraints> + <properties/> + <border type="none"/> + <children> + <component id="fd520" class="javax.swing.JLabel"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"> + <preferred-size width="95" height="-1"/> + </grid> + </constraints> + <properties> + <text value="Name:"/> + </properties> + </component> + <component id="7e88" class="javax.swing.JTextField" binding="myName"> + <constraints> + <grid row="0" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties/> + </component> + <component id="ec56c" class="javax.swing.JLabel"> + <constraints> + <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="9" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Description"/> + </properties> + </component> + <component id="2e2d7" class="javax.swing.JLabel"> + <constraints> + <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"> + <preferred-size width="95" height="-1"/> + </grid> + </constraints> + <properties> + <text value="Author:"/> + </properties> + </component> + <component id="41fe6" class="javax.swing.JTextField" binding="myAuthorField"> + <constraints> + <grid row="1" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties/> + </component> + <grid id="12f06" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="2" column="1" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <border type="line"> + <color color="-6709600"/> + </border> + <children> + <component id="389a7" class="javax.swing.JTextArea" binding="myDescription"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="50"/> + </grid> + </constraints> + <properties/> + </component> + </children> + </grid> + </children> + </grid> +</form> diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.java new file mode 100644 index 000000000000..83a3458576b6 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CCNewProjectPanel.java @@ -0,0 +1,59 @@ +package org.jetbrains.plugins.coursecreator.ui; + +import com.intellij.facet.ui.FacetValidatorsManager; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.DocumentAdapter; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; + +public class CCNewProjectPanel { + private JPanel myPanel; + private JTextArea myDescription; + private JTextField myName; + private JTextField myAuthorField; + private FacetValidatorsManager myValidationManager; + + + public CCNewProjectPanel() { + final String userName = System.getProperty("user.name"); + if (userName != null) { + myAuthorField.setText(userName); + } + myName.getDocument().addDocumentListener(new MyValidator()); + myDescription.getDocument().addDocumentListener(new MyValidator()); + myAuthorField.getDocument().addDocumentListener(new MyValidator()); + } + + public JPanel getMainPanel() { + return myPanel; + } + + @NotNull + public String getName() { + return StringUtil.notNullize(myName.getText()); + } + + @NotNull + public String getDescription() { + return StringUtil.notNullize(myDescription.getText()); + } + + @NotNull + public String getAuthor() { + return StringUtil.notNullize(myAuthorField.getText()); + } + + public void registerValidators(FacetValidatorsManager manager) { + myValidationManager = manager; + } + + private class MyValidator extends DocumentAdapter { + + @Override + protected void textChanged(DocumentEvent e) { + myValidationManager.validate(); + } + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchiveDialog.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchiveDialog.java new file mode 100644 index 000000000000..d6c3bdb24af3 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchiveDialog.java @@ -0,0 +1,40 @@ +package org.jetbrains.plugins.coursecreator.ui; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.coursecreator.actions.CreateCourseArchive; + +import javax.swing.*; + +public class CreateCourseArchiveDialog extends DialogWrapper { + + private CreateCourseArchivePanel myPanel; + private CreateCourseArchive myAction; + + public CreateCourseArchiveDialog(@NotNull final Project project, CreateCourseArchive action) { + super(project); + setTitle("Create Course Archive"); + myPanel = new CreateCourseArchivePanel(project, this); + myAction = action; + init(); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return myPanel; + } + + public void enableOKAction(boolean isEnabled) { + myOKAction.setEnabled(isEnabled); + } + + @Override + protected void doOKAction() { + myAction.setZipName(myPanel.getZipName()); + myAction.setLocationDir(myPanel.getLocationPath()); + super.doOKAction(); + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form new file mode 100644 index 000000000000..920dcb9494a7 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.plugins.coursecreator.ui.CreateCourseArchivePanel"> + <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <xy x="20" y="20" width="500" height="400"/> + </constraints> + <properties/> + <border type="none"/> + <children> + <grid id="a3b77" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <border type="none"/> + <children> + <component id="786af" class="javax.swing.JLabel"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Name:"/> + </properties> + </component> + <component id="160bb" class="javax.swing.JTextField" binding="myNameField" default-binding="true"> + <constraints> + <grid row="0" column="1" row-span="2" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties/> + </component> + <component id="628ab" class="javax.swing.JLabel"> + <constraints> + <grid row="1" column="0" row-span="2" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Location:"/> + </properties> + </component> + <component id="aab8" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myLocationField"> + <constraints> + <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + </component> + <grid id="29be7" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="3" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <border type="none"/> + <children> + <component id="4fa15" class="javax.swing.JLabel" binding="myErrorIcon"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value=""/> + </properties> + </component> + <component id="4bdcf" class="javax.swing.JLabel" binding="myErrorLabel"> + <constraints> + <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value=""/> + </properties> + </component> + </children> + </grid> + </children> + </grid> + </children> + </grid> +</form> diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.java new file mode 100644 index 000000000000..4e7deedf7e47 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.java @@ -0,0 +1,65 @@ +package org.jetbrains.plugins.coursecreator.ui; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +public class CreateCourseArchivePanel extends JPanel { + private JPanel myPanel; + private JTextField myNameField; + private TextFieldWithBrowseButton myLocationField; + private JLabel myErrorIcon; + private JLabel myErrorLabel; + private CreateCourseArchiveDialog myDlg; + + public CreateCourseArchivePanel(@NotNull final Project project, CreateCourseArchiveDialog dlg) { + setLayout(new BorderLayout()); + add(myPanel, BorderLayout.CENTER); + myErrorIcon.setIcon(AllIcons.Actions.Lightning); + setState(false); + myDlg = dlg; + myNameField.setText("course"); + myLocationField.setText(project.getBasePath()); + FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); + myLocationField.addBrowseFolderListener("Choose location folder", null, project, descriptor); + myLocationField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String location = myLocationField.getText(); + File file = new File(location); + if (!file.exists() || !file.isDirectory()) { + myDlg.enableOKAction(false); + setError("Invalid location"); + } + myDlg.enableOKAction(true); + } + }); + } + + private void setState(boolean isVisible) { + myErrorIcon.setVisible(isVisible); + myErrorLabel.setVisible(isVisible); + } + + private void setError(String message) { + myErrorLabel.setText(message); + setState(true); + } + + public String getZipName() { + return myNameField.getText(); + } + + public String getLocationPath() { + return myLocationField.getText(); + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java new file mode 100644 index 000000000000..c7e8f715672c --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java @@ -0,0 +1,153 @@ +package org.jetbrains.plugins.coursecreator.ui; + +import com.intellij.ide.projectView.ProjectView; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.ValidationInfo; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.coursecreator.CCProjectService; +import org.jetbrains.plugins.coursecreator.format.TaskWindow; + +import javax.swing.*; +import java.io.*; + +public class CreateTaskWindowDialog extends DialogWrapper { + + public static final String TITLE = "New Task Window"; + private static final Logger LOG = Logger.getInstance(CreateTaskWindowDialog.class.getName()); + private final TaskWindow myTaskWindow; + private final CreateTaskWindowPanel myPanel; + private final Project myProject; + + public Project getProject() { + return myProject; + } + + public CreateTaskWindowDialog(@NotNull final Project project, @NotNull final TaskWindow taskWindow, int lessonIndex, + int taskIndex, String taskFileName, int taskWindowIndex) { + super(project, true); + setTitle(TITLE); + myTaskWindow = taskWindow; + myPanel = new CreateTaskWindowPanel(this); + String generatedHintName = "lesson" + lessonIndex + "task" + taskIndex + taskFileName + "_" + taskWindowIndex; + myPanel.setGeneratedHintName(generatedHintName); + if (taskWindow.getHintName() != null) { + setHintText(project, taskWindow); + } + myProject = project; + String taskWindowTaskText = taskWindow.getTaskText(); + myPanel.setTaskWindowText(taskWindowTaskText != null ? taskWindowTaskText : ""); + String hintName = taskWindow.getHintName(); + myPanel.setHintName(hintName != null ? hintName : ""); + init(); + initValidation(); + } + + private void setHintText(Project project, TaskWindow taskWindow) { + VirtualFile hints = project.getBaseDir().findChild("hints"); + if (hints != null) { + File file = new File(hints.getPath(), taskWindow.getHintName()); + StringBuilder hintText = new StringBuilder(); + if (file.exists()) { + try { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + String line; + while ((line = bufferedReader.readLine()) != null) { + hintText.append(line).append("\n"); + } + myPanel.doClick(); + //myPanel.enableHint(true); + myPanel.setHintText(hintText.toString()); + } + catch (FileNotFoundException e) { + LOG.error("created hint was not found", e); + } + catch (IOException e) { + LOG.error(e); + } + } + } + } + + @Override + protected void doOKAction() { + String taskWindowText = myPanel.getTaskWindowText(); + myTaskWindow.setTaskText(StringUtil.notNullize(taskWindowText)); + if (myPanel.createHint()) { + String hintName = myPanel.getHintName(); + myTaskWindow.setHint(hintName); + String hintText = myPanel.getHintText(); + createHint(hintName, hintText); + } + super.doOKAction(); + } + + private void createHint(String hintName, String hintText) { + VirtualFile hintsDir = myProject.getBaseDir().findChild("hints"); + if (hintsDir != null) { + File hintFile = new File(hintsDir.getPath(), hintName); + PrintWriter printWriter = null; + try { + printWriter = new PrintWriter(hintFile); + printWriter.print(hintText); + } + catch (FileNotFoundException e) { + //TODO:show error in UI + return; + } + finally { + if (printWriter != null) { + printWriter.close(); + } + } + } + VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + ProjectView.getInstance(myProject).refresh(); + } + + public void deleteHint() { + VirtualFile hintsDir = myProject.getBaseDir().findChild("hints"); + if (hintsDir != null) { + String hintName = myTaskWindow.getHintName(); + if (hintName == null) { + return; + } + File hintFile = new File(hintsDir.getPath(), hintName); + if (hintFile.exists()) { + CCProjectService.deleteProjectFile(hintFile, myProject); + myTaskWindow.setHint(null); + myPanel.resetHint(); + } + } + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return myPanel; + } + + @Nullable + @Override + public ValidationInfo doValidate() { + String name = myPanel.getHintName(); + VirtualFile hintsDir = myProject.getBaseDir().findChild("hints"); + if (hintsDir == null) { + return null; + } + VirtualFile child = hintsDir.findChild(name); + if (child == null) { + return null; + } + return myTaskWindow.getHintName() != null ? null : new ValidationInfo("Hint file with such filename already exists"); + } + + public void validateInput() { + super.initValidation(); + } +} diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.form b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.form new file mode 100644 index 000000000000..ccd91e4154b0 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.form @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.plugins.coursecreator.ui.CreateTaskWindowPanel"> + <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <xy x="20" y="20" width="500" height="400"/> + </constraints> + <properties/> + <border type="none"/> + <children> + <component id="aaa28" class="javax.swing.JLabel"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <labelFor value="b712"/> + <text value="Text:"/> + </properties> + </component> + <component id="d2e2f" class="javax.swing.JLabel" binding="myHintNameLabel"> + <constraints> + <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <enabled value="true"/> + <labelFor value="ddeb1"/> + <text value="Hint name:"/> + </properties> + </component> + <component id="ddeb1" class="javax.swing.JTextField" binding="myHintName"> + <constraints> + <grid row="2" column="1" row-span="1" col-span="3" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties> + <text value=""/> + </properties> + </component> + <component id="d322a" class="javax.swing.JLabel" binding="myHintTextLabel"> + <constraints> + <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <labelFor value="d0efc"/> + <text value="Hint text:"/> + </properties> + </component> + <grid id="51a63" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="3" column="1" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <border type="line"> + <color color="-6709600"/> + </border> + <children> + <component id="d0efc" class="javax.swing.JTextArea" binding="myHintText"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false"> + <preferred-size width="300" height="100"/> + </grid> + </constraints> + <properties/> + </component> + </children> + </grid> + <grid id="cbc70" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="0" column="1" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"> + <minimum-size width="150" height="-1"/> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties/> + <border type="line"> + <color color="-6709600"/> + </border> + <children> + <component id="b712" class="javax.swing.JTextArea" binding="myTaskWindowText"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false"> + <preferred-size width="300" height="100"/> + </grid> + </constraints> + <properties/> + </component> + </children> + </grid> + <component id="f86b4" class="javax.swing.JCheckBox" binding="myCreateHintCheckBox" default-binding="true"> + <constraints> + <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Create hint"/> + </properties> + </component> + </children> + </grid> +</form> diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java new file mode 100644 index 000000000000..21a7eb063c63 --- /dev/null +++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java @@ -0,0 +1,96 @@ +package org.jetbrains.plugins.coursecreator.ui; + +import com.intellij.ui.DocumentAdapter; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.awt.*; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +public class CreateTaskWindowPanel extends JPanel { + + private final CreateTaskWindowDialog myDialog; + private JPanel myPanel; + private JTextArea myTaskWindowText; + private JTextField myHintName; + private JTextArea myHintText; + private JCheckBox myCreateHintCheckBox; + private JLabel myHintNameLabel; + private JLabel myHintTextLabel; + private String myGeneratedHintName = ""; + + public CreateTaskWindowPanel(CreateTaskWindowDialog dialog) { + super(new BorderLayout()); + add(myPanel, BorderLayout.CENTER); + myDialog = dialog; + enableHint(false); + myCreateHintCheckBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + int state = e.getStateChange(); + // 1 for checked + enableHint(state == 1); + if (state == 2) { + myDialog.deleteHint(); + } + } + }); + + myHintName.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent e) { + myDialog.validateInput(); + } + }); + } + + public void enableHint(boolean isEnable) { + myHintName.setEnabled(isEnable); + myHintText.setEnabled(isEnable); + myHintNameLabel.setEnabled(isEnable); + myHintTextLabel.setEnabled(isEnable); + myHintName.setText(myGeneratedHintName); + } + + public void setTaskWindowText(String taskWindowText) { + myTaskWindowText.setText(taskWindowText); + } + + public void setHintName(String hintName) { + myHintName.setText(hintName); + } + + public void setHintText(String hintText) { + myHintText.setText(hintText); + } + + public String getTaskWindowText() { + return myTaskWindowText.getText(); + } + + public String getHintName() { + return myHintName.getText(); + } + + public String getHintText() { + return myHintText.getText(); + } + + public boolean createHint() { + return myHintName.isEnabled(); + } + + public void doClick() { + myCreateHintCheckBox.doClick(); + } + + public void resetHint() { + myHintName.setText(""); + myHintText.setText(""); + } + + public void setGeneratedHintName(String generatedHintName) { + myGeneratedHintName = generatedHintName; + } +} diff --git a/python/edu/learn-python/gen/icons/StudyIcons.java b/python/edu/learn-python/gen/icons/StudyIcons.java index 28409103ea00..04ae98da1672 100644 --- a/python/edu/learn-python/gen/icons/StudyIcons.java +++ b/python/edu/learn-python/gen/icons/StudyIcons.java @@ -13,17 +13,15 @@ public class StudyIcons { return IconLoader.getIcon(path, StudyIcons.class); } - public static final Icon Add = load("/icons/com/jetbrains/python/edu/add.png"); // 16x16 - public static final Icon Checked = load("/icons/com/jetbrains/python/edu/checked.png"); // 32x32 - public static final Icon Failed = load("/icons/com/jetbrains/python/edu/failed.png"); // 32x32 - public static final Icon Next = load("/icons/com/jetbrains/python/edu/next.png"); // 24x24 - public static final Icon Playground = load("/icons/com/jetbrains/python/edu/playground.png"); // 32x28 - public static final Icon Prev = load("/icons/com/jetbrains/python/edu/prev.png"); // 24x24 - public static final Icon Refresh = load("/icons/com/jetbrains/python/edu/refresh.png"); // 16x16 - public static final Icon Refresh24 = load("/icons/com/jetbrains/python/edu/refresh24.png"); // 24x24 - public static final Icon Resolve = load("/icons/com/jetbrains/python/edu/resolve.png"); // 24x24 - public static final Icon Run = load("/icons/com/jetbrains/python/edu/Run.png"); // 24x24 + public static final Icon EducationalProjectType = load("/icons/com/jetbrains/python/edu/EducationalProjectType.png"); // 32x32 + public static final Icon Lesson = load("/icons/com/jetbrains/python/edu/Lesson.png"); // 16x16 + public static final Icon LessonCompl = load("/icons/com/jetbrains/python/edu/LessonCompl.png"); // 16x16 + public static final Icon Playground = load("/icons/com/jetbrains/python/edu/Playground.png"); // 16x16 + public static final Icon Prev = load("/icons/com/jetbrains/python/edu/prev.png"); // 16x16 + public static final Icon Resolve = load("/icons/com/jetbrains/python/edu/resolve.png"); // 16x16 public static final Icon ShowHint = load("/icons/com/jetbrains/python/edu/showHint.png"); // 24x24 - public static final Icon Unchecked = load("/icons/com/jetbrains/python/edu/unchecked.png"); // 32x32 + public static final Icon Task = load("/icons/com/jetbrains/python/edu/Task.png"); // 16x16 + public static final Icon TaskCompl = load("/icons/com/jetbrains/python/edu/TaskCompl.png"); // 16x16 + public static final Icon TaskProbl = load("/icons/com/jetbrains/python/edu/TaskProbl.png"); // 16x16 public static final Icon WatchInput = load("/icons/com/jetbrains/python/edu/WatchInput.png"); // 24x24 } diff --git a/python/edu/learn-python/learn-python.iml b/python/edu/learn-python/learn-python.iml index bd539d1e2147..613d67594a7a 100644 --- a/python/edu/learn-python/learn-python.iml +++ b/python/edu/learn-python/learn-python.iml @@ -15,6 +15,7 @@ <orderEntry type="module" module-name="lang-impl" /> <orderEntry type="library" name="gson" level="project" /> <orderEntry type="library" name="JUnit4" level="project" /> + <orderEntry type="module" module-name="python-ide-community" /> </component> </module> diff --git a/python/edu/learn-python/resources/META-INF/plugin.xml b/python/edu/learn-python/resources/META-INF/plugin.xml index ec828eb44d59..8e8bddcce5e5 100644 --- a/python/edu/learn-python/resources/META-INF/plugin.xml +++ b/python/edu/learn-python/resources/META-INF/plugin.xml @@ -50,7 +50,7 @@ <action id="NextTaskAction" class="com.jetbrains.python.edu.actions.StudyNextStudyTaskAction" text="NextTaskAction" description="Next Task"/> <action id="PreviousTaskAction" class="com.jetbrains.python.edu.actions.StudyPreviousStudyTaskAction" text="PreviousTaskAction" description="Previous Task"/> - <action id="RefreshTaskAction" class="com.jetbrains.python.edu.actions.StudyRefreshTaskAction" text="RefreshTaskAction" + <action id="RefreshTaskAction" class="com.jetbrains.python.edu.actions.StudyRefreshTaskFileAction" text="RefreshTaskAction" description="Refresh current task"/> <action id="WatchInputAction" class="com.jetbrains.python.edu.actions.StudyEditInputAction" text="WatchInputAction" description="watch input"/> @@ -59,6 +59,11 @@ description="show hint"> <add-to-group group-id="MainToolBar" anchor="last"/> </action> + + <action id="WelcomeScreen.LearnPython" class="com.jetbrains.python.edu.actions.StudyNewProject" icon="StudyIcons.EducationalProjectType"> + <add-to-group group-id="WelcomeScreen.QuickStart" anchor="first"/> + </action> + </actions> <extensions defaultExtensionNs="com.intellij"> @@ -70,4 +75,7 @@ <applicationService serviceInterface="com.intellij.openapi.fileEditor.impl.EditorEmptyTextPainter" serviceImplementation="com.jetbrains.python.edu.StudyInstructionPainter" overrides="true"/> </extensions> + <extensions defaultExtensionNs="Pythonid"> + <visitorFilter language="Python" implementationClass="com.jetbrains.python.edu.highlighting.StudyVisitorFilter"/> + </extensions> </idea-plugin>
\ No newline at end of file diff --git a/python/edu/learn-python/resources/courses/introduction_course.zip b/python/edu/learn-python/resources/courses/introduction_course.zip Binary files differindex f3b24f24b1e2..c39695e0c380 100644 --- a/python/edu/learn-python/resources/courses/introduction_course.zip +++ b/python/edu/learn-python/resources/courses/introduction_course.zip diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType.png Binary files differnew file mode 100644 index 000000000000..6340e89b1f3f --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType_dark.png Binary files differnew file mode 100644 index 000000000000..d9ec6dc45b0c --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/EducationalProjectType_dark.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson.png Binary files differnew file mode 100644 index 000000000000..9cc8a4f84f06 --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson@2x.png Binary files differnew file mode 100644 index 000000000000..2b0c0b83271b --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Lesson@2x.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonComp@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonComp@2x.png Binary files differnew file mode 100644 index 000000000000..e18c6391d0ac --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonComp@2x.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonCompl.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonCompl.png Binary files differnew file mode 100644 index 000000000000..bacc7ded6755 --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/LessonCompl.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground.png Binary files differnew file mode 100644 index 000000000000..10867103374a --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground@2x.png Binary files differnew file mode 100644 index 000000000000..58665fa41a1b --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Playground@2x.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Run.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Run.png Binary files differdeleted file mode 100644 index 27a6e362cded..000000000000 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Run.png +++ /dev/null diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task.png Binary files differnew file mode 100644 index 000000000000..b678c649b303 --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x.png Binary files differnew file mode 100644 index 000000000000..4abb95ce7c9e --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x_dark.png Binary files differnew file mode 100644 index 000000000000..78af6914c933 --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task@2x_dark.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl.png Binary files differnew file mode 100644 index 000000000000..a7f8f7792823 --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x.png Binary files differnew file mode 100644 index 000000000000..d657aa68f7df --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x_dark.png Binary files differnew file mode 100644 index 000000000000..f5f29ee3e68e --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl@2x_dark.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl_dark.png Binary files differnew file mode 100644 index 000000000000..481e9cd2fbbd --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskCompl_dark.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl.png Binary files differnew file mode 100644 index 000000000000..6173d6423385 --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x.png Binary files differnew file mode 100644 index 000000000000..44aba9d1c6fb --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x_dark.png Binary files differnew file mode 100644 index 000000000000..f74c9de5398c --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl@2x_dark.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl_dark.png Binary files differnew file mode 100644 index 000000000000..0133a7f75505 --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/TaskProbl_dark.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task_dark.png Binary files differnew file mode 100644 index 000000000000..2ff286dd28f6 --- /dev/null +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Task_dark.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/add.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/add.png Binary files differdeleted file mode 100644 index 9494f2d0c72e..000000000000 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/add.png +++ /dev/null diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/checked.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/checked.png Binary files differdeleted file mode 100644 index 4105a01f1353..000000000000 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/checked.png +++ /dev/null diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/failed.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/failed.png Binary files differdeleted file mode 100644 index e2aaa556056e..000000000000 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/failed.png +++ /dev/null diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/icon.jpg b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/icon.jpg Binary files differdeleted file mode 100644 index 3a9716e4e1fb..000000000000 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/icon.jpg +++ /dev/null diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/next.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/next.png Binary files differdeleted file mode 100644 index dd1a5d9aebf3..000000000000 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/next.png +++ /dev/null diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/playground.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/playground.png Binary files differdeleted file mode 100644 index d12a751c0c40..000000000000 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/playground.png +++ /dev/null diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/prev.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/prev.png Binary files differindex 0656f81eee83..fc51cb5861b1 100644 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/prev.png +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/prev.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh.png Binary files differdeleted file mode 100644 index d595f6b42f56..000000000000 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh.png +++ /dev/null diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh24.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh24.png Binary files differdeleted file mode 100644 index 218f075d0c18..000000000000 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh24.png +++ /dev/null diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve.png Binary files differindex 7ef960bcf244..78290f936f68 100644 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve.png +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve_dark.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve_dark.png Binary files differindex 99aaa1d20a32..b988adc56843 100644 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve_dark.png +++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve_dark.png diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/unchecked.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/unchecked.png Binary files differdeleted file mode 100644 index 2145982cf2be..000000000000 --- a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/unchecked.png +++ /dev/null diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java index d4831d93e367..59bd8bc2ea7c 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java @@ -53,7 +53,7 @@ public class StudyDirectoryProjectGenerator extends PythonProjectGenerator imple @NotNull @Override public String getName() { - return "Study project"; + return "Learn Python"; } @@ -137,7 +137,7 @@ public class StudyDirectoryProjectGenerator extends PythonProjectGenerator imple @Nullable @Override public Icon getLogo() { - return StudyIcons.Playground; + return StudyIcons.EducationalProjectType; } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDocumentListener.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDocumentListener.java index 9fdcf704a29b..6ce1d0991dff 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDocumentListener.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDocumentListener.java @@ -52,6 +52,9 @@ public class StudyDocumentListener extends DocumentAdapter { if (myTaskWindow != null) { int newLength = myTaskWindow.getLength() + change; myTaskWindow.setLength(newLength <= 0 ? 0 : newLength); + if (e.getNewFragment().equals("\n")) { + myTaskWindow.setLength(myTaskWindow.getLength() + 1); + } } int newEnd = offset + event.getNewLength(); int newLine = document.getLineNumber(newEnd); diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInstructionPainter.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInstructionPainter.java index 4f34bfb92fab..96a44b2ee66a 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInstructionPainter.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInstructionPainter.java @@ -8,7 +8,6 @@ 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.*; @@ -19,10 +18,6 @@ import java.awt.*; 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); diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyState.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyState.java new file mode 100644 index 000000000000..96dc3d906472 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyState.java @@ -0,0 +1,52 @@ +package com.jetbrains.python.edu; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.python.edu.course.Task; +import com.jetbrains.python.edu.course.TaskFile; +import com.jetbrains.python.edu.editor.StudyEditor; + +public class StudyState { + private final StudyEditor myStudyEditor; + private final Editor myEditor; + private final TaskFile myTaskFile; + private final VirtualFile myVirtualFile; + private final Task myTask; + private final VirtualFile myTaskDir; + + public StudyState(final StudyEditor studyEditor) { + myStudyEditor = studyEditor; + myEditor = studyEditor != null ? studyEditor.getEditor() : null; + myTaskFile = studyEditor != null ? studyEditor.getTaskFile() : null; + myVirtualFile = myEditor != null ? FileDocumentManager.getInstance().getFile(myEditor.getDocument()) : null; + myTaskDir = myVirtualFile != null ? myVirtualFile.getParent() : null; + myTask = myTaskFile != null ? myTaskFile.getTask() : null; + } + + public Editor getEditor() { + return myEditor; + } + + public TaskFile getTaskFile() { + return myTaskFile; + } + + public VirtualFile getVirtualFile() { + return myVirtualFile; + } + + public Task getTask() { + return myTask; + } + + public VirtualFile getTaskDir() { + return myTaskDir; + } + + public boolean isValid() { + return myStudyEditor != null && myEditor != null && + myTaskFile != null && myVirtualFile != null && + myTask != null && myTaskDir != null; + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTaskManager.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTaskManager.java index 213c1f7601f0..3013fbcb05d3 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTaskManager.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTaskManager.java @@ -109,21 +109,29 @@ public class StudyTaskManager implements ProjectComponent, PersistentStateCompon StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new Runnable() { @Override public void run() { - ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW).show(null); - FileEditor[] editors = FileEditorManager.getInstance(myProject).getSelectedEditors(); - if (editors.length > 0) { - JComponent focusedComponent = editors[0].getPreferredFocusedComponent(); - if (focusedComponent != null) { - IdeFocusManager.getInstance(myProject).requestFocus(focusedComponent, true); + ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW).show(new Runnable() { + @Override + public void run() { + FileEditor[] editors = FileEditorManager.getInstance(myProject).getSelectedEditors(); + if (editors.length > 0) { + final JComponent focusedComponent = editors[0].getPreferredFocusedComponent(); + if (focusedComponent != null) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + IdeFocusManager.getInstance(myProject).requestFocus(focusedComponent, true); + } + }); + } + } } - } + }); } }); 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, diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java new file mode 100644 index 000000000000..b0cd5ba89fed --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java @@ -0,0 +1,76 @@ +package com.jetbrains.python.edu; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.python.edu.course.Course; +import com.jetbrains.python.edu.course.Task; +import com.jetbrains.python.sdk.PythonSdkType; + +import java.io.*; +import java.util.Map; + +public class StudyTestRunner { + public static final String TEST_OK = "#study_plugin test OK"; + private static final String TEST_FAILED = "#study_plugin FAILED + "; + private static final String PYTHONPATH = "PYTHONPATH"; + private static final Logger LOG = Logger.getInstance(StudyTestRunner.class); + private final Task myTask; + private final VirtualFile myTaskDir; + + public StudyTestRunner(Task task, VirtualFile taskDir) { + myTask = task; + myTaskDir = taskDir; + } + + public 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(FileUtil.toSystemDependentName(executablePath)); + return commandLine.createProcess(); + } + } + return null; + } + + + public 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 TEST_OK; + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java index d3ac1dadf98e..5d9bb139db09 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java @@ -3,6 +3,7 @@ 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.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; @@ -10,12 +11,12 @@ 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.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.util.ui.UIUtil; -import com.jetbrains.python.edu.course.TaskFile; -import com.jetbrains.python.edu.course.TaskWindow; +import com.jetbrains.python.edu.course.*; import com.jetbrains.python.edu.editor.StudyEditor; import com.jetbrains.python.edu.ui.StudyToolWindowFactory; import org.jetbrains.annotations.NotNull; @@ -70,7 +71,7 @@ public class StudyUtils { return wrapHTML ? UIUtil.toHtml(taskText.toString()) : taskText.toString(); } catch (IOException e) { - LOG.error("Failed to get file text from file " + fileName, e); + LOG.info("Failed to get file text from file " + fileName, e); } finally { closeSilently(reader); @@ -119,14 +120,18 @@ public class StudyUtils { } @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - public static VirtualFile flushWindows(Document document, TaskFile taskFile, VirtualFile file) { + public static VirtualFile flushWindows(TaskFile taskFile, VirtualFile file) { VirtualFile taskDir = file.getParent(); VirtualFile fileWindows = null; + final Document document = FileDocumentManager.getInstance().getDocument(file); + if (document == null) { + LOG.debug("Couldn't flush windows"); + return null; + } if (taskDir != null) { String name = file.getNameWithoutExtension() + "_windows"; PrintWriter printWriter = null; try { - fileWindows = taskDir.createChildData(taskFile, name); printWriter = new PrintWriter(new FileOutputStream(fileWindows.getPath())); for (TaskWindow taskWindow : taskFile.getTaskWindows()) { @@ -137,6 +142,12 @@ public class StudyUtils { String windowDescription = document.getText(new TextRange(start, start + taskWindow.getLength())); printWriter.println("#study_plugin_window = " + windowDescription); } + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + FileDocumentManager.getInstance().saveDocument(document); + } + }); } catch (IOException e) { LOG.error(e); @@ -148,4 +159,27 @@ public class StudyUtils { } return fileWindows; } + + public static void deleteFile(VirtualFile file) { + try { + file.delete(StudyUtils.class); + } + catch (IOException e) { + LOG.error(e); + } + } + + public static File copyResourceFile(String sourceName, String copyName, Project project, Task task) + throws IOException { + StudyTaskManager taskManager = StudyTaskManager.getInstance(project); + Course course = taskManager.getCourse(); + 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, copyName); + FileUtil.copy(new File(pathToResource, sourceName), resourceFile); + return resourceFile; + } } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java index f8e10c9c4521..5d02f7149aab 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java @@ -1,7 +1,6 @@ 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; @@ -13,93 +12,81 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; -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.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.util.TextRange; -import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.JBColor; +import com.intellij.openapi.wm.IdeFocusManager; import com.jetbrains.python.edu.StudyDocumentListener; -import com.jetbrains.python.edu.StudyTaskManager; +import com.jetbrains.python.edu.StudyState; +import com.jetbrains.python.edu.StudyTestRunner; import com.jetbrains.python.edu.StudyUtils; -import com.jetbrains.python.edu.course.*; +import com.jetbrains.python.edu.course.StudyStatus; +import com.jetbrains.python.edu.course.Task; +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.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; -import java.io.*; -import java.util.*; -import java.util.List; +import java.io.IOException; +import java.util.Map; public class StudyCheckAction extends DumbAwareAction { private static final Logger LOG = Logger.getInstance(StudyCheckAction.class.getName()); - public static final String PYTHONPATH = "PYTHONPATH"; + private static final String ANSWERS_POSTFIX = "_answers.py"; - static 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; + private static void flushWindows(@NotNull final Task task, @NotNull final VirtualFile taskDir) { + for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) { + String name = entry.getKey(); + TaskFile taskFile = entry.getValue(); + VirtualFile virtualFile = taskDir.findChild(name); + if (virtualFile == null) { + continue; + } + StudyUtils.flushWindows(taskFile, virtualFile); } + } - 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(FileUtil.toSystemDependentName(executablePath)); - return commandLine.createProcess(); - } + private static void deleteWindowDescriptions(@NotNull final Task task, @NotNull final VirtualFile taskDir) { + for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) { + String name = entry.getKey(); + VirtualFile virtualFile = taskDir.findChild(name); + if (virtualFile == null) { + continue; + } + String windowsFileName = virtualFile.getNameWithoutExtension() + "_windows"; + VirtualFile windowsFile = taskDir.findChild(windowsFileName); + if (windowsFile != null) { + StudyUtils.deleteFile(windowsFile); } - 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); + private static void drawAllTaskWindows(@NotNull final Project project, @NotNull final Task task, @NotNull final VirtualFile taskDir) { + for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) { + String name = entry.getKey(); + TaskFile taskFile = entry.getValue(); + VirtualFile virtualFile = taskDir.findChild(name); + if (virtualFile == null) { + continue; } - finally { - StudyUtils.closeSilently(testOutputReader); + FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile); + if (fileEditor instanceof StudyEditor) { + StudyEditor studyEditor = (StudyEditor)fileEditor; + taskFile.drawAllWindows(studyEditor.getEditor()); } - return TEST_OK; } } + public void check(@NotNull final Project project) { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override @@ -107,188 +94,138 @@ public class StudyCheckAction extends DumbAwareAction { CommandProcessor.getInstance().runUndoTransparentAction(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); - List<VirtualFile> filesToDelete = new ArrayList<VirtualFile>(); - if (selectedTaskFile != null) { - final VirtualFile taskDir = openedFile.getParent(); - Task currentTask = selectedTaskFile.getTask(); - StudyStatus oldStatus = currentTask.getStatus(); - Map<String, TaskFile> taskFiles = selectedTaskFile.getTask().getTaskFiles(); + final StudyEditor selectedEditor = StudyEditor.getSelectedStudyEditor(project); + final StudyState studyState = new StudyState(selectedEditor); + if (!studyState.isValid()) { + LOG.error("StudyCheckAction was invokes outside study editor"); + return; + } + Task task = studyState.getTask(); + StudyStatus oldStatus = task.getStatus(); + Map<String, TaskFile> taskFiles = task.getTaskFiles(); + VirtualFile taskDir = studyState.getTaskDir(); + flushWindows(task, taskDir); + StudyRunAction runAction = (StudyRunAction)ActionManager.getInstance().getAction(StudyRunAction.ACTION_ID); + if (runAction != null && taskFiles.size() == 1) { + runAction.run(project); + } + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + IdeFocusManager.getInstance(project).requestFocus(studyState.getEditor().getComponent(), true); + } + }); + final StudyTestRunner testRunner = new StudyTestRunner(task, taskDir); + Process testProcess = null; + try { + testProcess = testRunner.launchTests(project, studyState.getVirtualFile().getPath()); + } + catch (ExecutionException e) { + LOG.error(e); + } + if (testProcess == null) { + return; + } + String failedMessage = testRunner.getPassedTests(testProcess); + if (failedMessage.equals(StudyTestRunner.TEST_OK)) { + task.setStatus(StudyStatus.Solved, oldStatus); + createTestResultPopUp("Congratulations!", MessageType.INFO.getPopupBackground(), project); + } + else { + task.setStatus(StudyStatus.Failed, oldStatus); for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) { String name = entry.getKey(); TaskFile taskFile = entry.getValue(); - VirtualFile virtualFile = taskDir.findChild(name); - if (virtualFile == null) { + if (taskFile.getTaskWindows().size() < 2) { + taskFile.setStatus(StudyStatus.Failed, StudyStatus.Unchecked); continue; } - VirtualFile windowFile = StudyUtils.flushWindows(FileDocumentManager.getInstance().getDocument(virtualFile), taskFile, virtualFile); - filesToDelete.add(windowFile); - FileDocumentManager.getInstance().saveAllDocuments(); - } - - StudyRunAction runAction = (StudyRunAction)ActionManager.getInstance().getAction(StudyRunAction.ACTION_ID); - if (runAction != null && currentTask.getTaskFiles().size() == 1) { - 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, oldStatus); - StudyUtils.updateStudyToolWindow(project); - selectedTaskFile.drawAllWindows(selectedEditor); - ProjectView.getInstance(project).refresh(); - for (VirtualFile file:filesToDelete) { - try { - file.delete(this); - } - catch (IOException e) { - LOG.error(e); - } - } - createTestResultPopUp("Congratulations!", JBColor.GREEN, project); - return; - } - for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) { - String name = entry.getKey(); - TaskFile taskFile = entry.getValue(); - TaskFile answerTaskFile = new TaskFile(); - VirtualFile virtualFile = taskDir.findChild(name); - if (virtualFile == null) { - continue; - } - VirtualFile answerFile = getCopyWithAnswers(taskDir, virtualFile, taskFile, answerTaskFile); - for (TaskWindow taskWindow : answerTaskFile.getTaskWindows()) { - Document document = FileDocumentManager.getInstance().getDocument(virtualFile); - if (document == null) { - continue; - } - if (!taskWindow.isValid(document)) { - continue; - } - check(project, taskWindow, answerFile, answerTaskFile, taskFile, document, testRunner, virtualFile); - } - FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile); - Editor editor = null; - if (fileEditor instanceof StudyEditor) { - StudyEditor studyEditor = (StudyEditor) fileEditor; - editor = studyEditor.getEditor(); - } - - if (editor != null) { - taskFile.drawAllWindows(editor); - StudyUtils.synchronize(); - } - try { - answerFile.delete(this); - } - catch (IOException e) { - LOG.error(e); - } - } - for (VirtualFile file:filesToDelete) { - try { - file.delete(this); - } - catch (IOException e) { - LOG.error(e); - } - } - currentTask.setStatus(StudyStatus.Failed, oldStatus); - StudyUtils.updateStudyToolWindow(project); - createTestResultPopUp(failedMessage, JBColor.RED, project); + runSmartTestProcess(taskDir, testRunner, name, taskFile, project); } + createTestResultPopUp(failedMessage, MessageType.ERROR.getPopupBackground(), project); + navigateToFailedTaskWindow(studyState, task, taskDir, project); } + StudyUtils.updateStudyToolWindow(project); + drawAllTaskWindows(project, task, taskDir); + ProjectView.getInstance(project).refresh(); + deleteWindowDescriptions(task, taskDir); } - } - - } - }); + }); } }); } - 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(), answerFile.getNameWithoutExtension() + "_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); + private static void navigateToFailedTaskWindow(@NotNull final StudyState studyState, + @NotNull final Task task, + @NotNull final VirtualFile taskDir, + @NotNull final Project project) { + TaskFile selectedTaskFile = studyState.getTaskFile(); + Editor editor = studyState.getEditor(); + TaskFile taskFileToNavigate = selectedTaskFile; + VirtualFile fileToNavigate = studyState.getVirtualFile(); + if (!selectedTaskFile.hasFailedTaskWindows()) { + for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) { + String name = entry.getKey(); + TaskFile taskFile = entry.getValue(); + if (taskFile.hasFailedTaskWindows()) { + taskFileToNavigate = taskFile; + VirtualFile virtualFile = taskDir.findChild(name); + if (virtualFile == null) { + continue; } - }); - 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, StudyStatus.Unchecked); - windowCopy.delete(this); - fileWindows.delete(this); - if (!resourceFile.delete()) { - LOG.error("failed to delete", resourceFile.getPath()); + FileEditor fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile); + if (fileEditor instanceof StudyEditor) { + StudyEditor studyEditor = (StudyEditor)fileEditor; + editor = studyEditor.getEditor(); + } + fileToNavigate = virtualFile; + break; } } } - catch (IOException e) { - LOG.error(e); + FileEditorManager.getInstance(project).openFile(fileToNavigate, true); + final Editor editorToNavigate = editor; + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + IdeFocusManager.getInstance(project).requestFocus(editorToNavigate.getContentComponent(), true); + } + }); + taskFileToNavigate.navigateToFirstFailedTaskWindow(editor); + } + + private void runSmartTestProcess(@NotNull final VirtualFile taskDir, + @NotNull final StudyTestRunner testRunner, + final String taskFileName, + @NotNull final TaskFile taskFile, + @NotNull final Project project) { + TaskFile answerTaskFile = new TaskFile(); + VirtualFile virtualFile = taskDir.findChild(taskFileName); + if (virtualFile == null) { + return; } - catch (ExecutionException e) { - LOG.error(e); + VirtualFile answerFile = getCopyWithAnswers(taskDir, virtualFile, taskFile, answerTaskFile); + for (TaskWindow taskWindow : answerTaskFile.getTaskWindows()) { + Document document = FileDocumentManager.getInstance().getDocument(virtualFile); + if (document == null) { + continue; + } + if (!taskWindow.isValid(document)) { + continue; + } + taskWindow.smartCheck(project, answerFile, answerTaskFile, taskFile, testRunner, virtualFile, document); } + StudyUtils.deleteFile(answerFile); } - - private VirtualFile getCopyWithAnswers(final VirtualFile taskDir, - final VirtualFile file, - final TaskFile source, - TaskFile target) { + private VirtualFile getCopyWithAnswers(@NotNull final VirtualFile taskDir, + @NotNull final VirtualFile file, + @NotNull final TaskFile source, + @NotNull final TaskFile target) { VirtualFile copy = null; try { - copy = file.copy(this, taskDir, file.getNameWithoutExtension() +"_answers.py"); + copy = file.copy(this, taskDir, file.getNameWithoutExtension() + ANSWERS_POSTFIX); final FileDocumentManager documentManager = FileDocumentManager.getInstance(); final Document document = documentManager.getDocument(copy); if (document != null) { @@ -315,19 +252,18 @@ public class StudyCheckAction extends DumbAwareAction { catch (IOException e) { LOG.error(e); } - - return copy; } private static void createTestResultPopUp(final String text, Color color, @NotNull final Project project) { BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(text, null, color, null); - Balloon balloon = balloonBuilder.createBalloon(); + final Balloon balloon = balloonBuilder.createBalloon(); StudyEditor studyEditor = StudyEditor.getSelectedStudyEditor(project); assert studyEditor != null; JButton checkButton = studyEditor.getCheckButton(); balloon.showInCenterOf(checkButton); + Disposer.register(project, balloon); } @Override diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java index 5b9a6fef23ac..72660fc9c2c6 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java @@ -19,6 +19,7 @@ import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.tabs.TabInfo; import com.intellij.ui.tabs.TabsListener; import com.intellij.ui.tabs.impl.JBEditorTabs; +import com.intellij.util.PlatformIcons; import com.jetbrains.python.edu.StudyTaskManager; import com.jetbrains.python.edu.StudyUtils; import com.jetbrains.python.edu.course.Task; @@ -26,7 +27,6 @@ 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.StudyTestContentPanel; -import icons.StudyIcons; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -92,7 +92,7 @@ public class StudyEditInputAction extends DumbAwareAction { i++; } TabInfo plusTab = new TabInfo(new JPanel()); - plusTab.setIcon(StudyIcons.Add); + plusTab.setIcon(PlatformIcons.ADD_ICON); tabbedPane.addTabSilently(plusTab, tabbedPane.getTabCount()); final JBPopup hint = JBPopupFactory.getInstance().createComponentPopupBuilder(tabbedPane.getComponent(), tabbedPane.getComponent()) diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java new file mode 100644 index 000000000000..0b75c4bc01c4 --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.edu.actions; + +import com.jetbrains.python.edu.StudyDirectoryProjectGenerator; +import com.jetbrains.python.newProject.actions.GenerateProjectCallback; +import com.jetbrains.python.newProject.actions.ProjectSpecificAction; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class StudyNewProject extends ProjectSpecificAction { + + public StudyNewProject(@NotNull final String name, @Nullable final Runnable runnable) { + super(new GenerateProjectCallback(runnable), new StudyDirectoryProjectGenerator(), name, true); + } + + public StudyNewProject() { + this("Learn Python", null); + } + +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java index 81818a95c044..3c971c3fe15e 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java @@ -2,13 +2,14 @@ package com.jetbrains.python.edu.actions; import com.jetbrains.python.edu.editor.StudyEditor; import com.jetbrains.python.edu.course.Task; +import org.jetbrains.annotations.NotNull; import javax.swing.*; public class StudyNextStudyTaskAction extends StudyTaskNavigationAction { @Override - protected JButton getButton(StudyEditor selectedStudyEditor) { + protected JButton getButton(@NotNull final StudyEditor selectedStudyEditor) { return selectedStudyEditor.getNextTaskButton(); } @@ -18,7 +19,7 @@ public class StudyNextStudyTaskAction extends StudyTaskNavigationAction { } @Override - protected Task getTargetTask(Task sourceTask) { + protected Task getTargetTask(@NotNull final Task sourceTask) { return sourceTask.next(); } }
\ No newline at end of file diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java index 595aeeff42e3..fcf9ef40c7d4 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java @@ -1,8 +1,8 @@ package com.jetbrains.python.edu.actions; +import com.intellij.icons.AllIcons; import com.jetbrains.python.edu.StudyUtils; import com.jetbrains.python.edu.course.TaskWindow; -import icons.StudyIcons; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -16,7 +16,7 @@ public class StudyNextWindowAction extends StudyWindowNavigationAction { public static final String SHORTCUT2 = "ctrl pressed ENTER"; public StudyNextWindowAction() { - super("NextWindowAction", "Select next window", StudyIcons.Next); + super("NextWindowAction", "Select next window", AllIcons.Actions.Forward); } @Override diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java index bc26c28cfabd..f6da6a067894 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java @@ -3,13 +3,14 @@ package com.jetbrains.python.edu.actions; import com.jetbrains.python.edu.editor.StudyEditor; import com.jetbrains.python.edu.course.Task; +import org.jetbrains.annotations.NotNull; import javax.swing.*; public class StudyPreviousStudyTaskAction extends StudyTaskNavigationAction { @Override - protected JButton getButton(StudyEditor selectedStudyEditor) { + protected JButton getButton(@NotNull final StudyEditor selectedStudyEditor) { return selectedStudyEditor.getPrevTaskButton(); } @@ -19,7 +20,7 @@ public class StudyPreviousStudyTaskAction extends StudyTaskNavigationAction { } @Override - protected Task getTargetTask(Task sourceTask) { + protected Task getTargetTask(@NotNull final Task sourceTask) { return sourceTask.prev(); } }
\ No newline at end of file diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java index f8abb0b63365..a9448ddea0e3 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java @@ -14,6 +14,7 @@ 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.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; import com.jetbrains.python.edu.StudyDocumentListener; @@ -24,8 +25,8 @@ import com.jetbrains.python.edu.editor.StudyEditor; import java.io.*; -public class StudyRefreshTaskAction extends DumbAwareAction { - private static final Logger LOG = Logger.getInstance(StudyRefreshTaskAction.class.getName()); +public class StudyRefreshTaskFileAction extends DumbAwareAction { + private static final Logger LOG = Logger.getInstance(StudyRefreshTaskFileAction.class.getName()); public void refresh(final Project project) { ApplicationManager.getApplication().invokeLater(new Runnable() { @@ -92,14 +93,20 @@ public class StudyRefreshTaskAction extends DumbAwareAction { document.addDocumentListener(listener); } selectedTaskFile.drawAllWindows(editor); - IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true); + } + }); selectedTaskFile.navigateToFirstTaskWindow(editor); BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can now start again", MessageType.INFO, null); - Balloon balloon = balloonBuilder.createBalloon(); + final Balloon balloon = balloonBuilder.createBalloon(); StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project); assert selectedStudyEditor != null; balloon.showInCenterOf(selectedStudyEditor.getRefreshButton()); + Disposer.register(project, balloon); } catch (FileNotFoundException e1) { LOG.error(e1); diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java index 1efa90889449..2952486274cf 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java @@ -5,19 +5,18 @@ 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.openapi.util.Disposer; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; +import com.jetbrains.python.edu.StudyState; 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 icons.StudyIcons; @@ -33,58 +32,56 @@ public class StudyShowHintAction extends DumbAwareAction { } public void actionPerformed(AnActionEvent e) { - Project project = e.getProject(); - if (project != null) { + final Project project = e.getProject(); + if (project == null) { + return; + } + Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course == null) { + return; + } + StudyState studyState = new StudyState(StudyEditor.getSelectedStudyEditor(project)); + if (!studyState.isValid()) { + return; + } + PsiFile file = PsiManager.getInstance(project).findFile(studyState.getVirtualFile()); + final Editor editor = studyState.getEditor(); + LogicalPosition pos = editor.getCaretModel().getLogicalPosition(); + TaskWindow taskWindow = studyState.getTaskFile().getTaskWindow(editor.getDocument(), pos); + if (file == null || taskWindow == null) { + return; + } + String hint = taskWindow.getHint(); + if (hint == null) { + return; + } + File resourceFile = new File(course.getResourcePath()); + File resourceRoot = resourceFile.getParentFile(); + if (resourceRoot == null || !resourceRoot.exists()) { + return; + } + File hintsDir = new File(resourceRoot, Course.HINTS_DIR); + if (hintsDir.exists()) { + String hintText = StudyUtils.getFileText(hintsDir.getAbsolutePath(), hint, true); + int offset = editor.getDocument().getLineStartOffset(pos.line) + pos.column; + PsiElement element = file.findElementAt(offset); + if (hintText == null || element == null) { + return; + } + 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); - } - } - } - } - } - } - } - } - } + 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(editor); + Disposer.dispose(component); } } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java index b781e7da8849..46c0981cb964 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java @@ -1,8 +1,6 @@ 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; @@ -11,37 +9,34 @@ 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.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.jetbrains.python.edu.StudyState; 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 org.jetbrains.annotations.NotNull; import javax.swing.*; import java.util.Map; -/** - * author: liana - * data: 7/21/14. - */ + abstract public class StudyTaskNavigationAction 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); + public void navigateTask(@NotNull final Project project) { + StudyEditor studyEditor = StudyEditor.getSelectedStudyEditor(project); + StudyState studyState = new StudyState(studyEditor); + if (!studyState.isValid()) { + return; + } + Task nextTask = getTargetTask(studyState.getTask()); 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)); + assert studyEditor != null; + balloon.showInCenterOf(getButton(studyEditor)); return; } for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) { @@ -82,16 +77,24 @@ abstract public class StudyTaskNavigationAction extends DumbAwareAction { if (shouldBeActive != null) { FileEditorManager.getInstance(project).openFile(shouldBeActive, true); } + ToolWindow runToolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.RUN); + if (runToolWindow != null) { + runToolWindow.hide(null); + } } - protected abstract JButton getButton(StudyEditor selectedStudyEditor); + protected abstract JButton getButton(@NotNull final StudyEditor selectedStudyEditor); @Override public void actionPerformed(AnActionEvent e) { - navigateTask(e.getProject()); + Project project = e.getProject(); + if (project == null) { + return; + } + navigateTask(project); } protected abstract String getNavigationFinishedMessage(); - protected abstract Task getTargetTask(Task sourceTask); + protected abstract Task getTargetTask(@NotNull final Task sourceTask); } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java index 4f17fc0d27f3..c46c4f56f937 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java @@ -21,7 +21,7 @@ import java.util.List; * which is visible to student in project view */ -public class TaskFile implements Stateful{ +public class TaskFile implements Stateful { public List<TaskWindow> taskWindows = new ArrayList<TaskWindow>(); private Task myTask; @Transient @@ -173,7 +173,18 @@ public class TaskFile implements Stateful{ for (TaskWindow w : taskWindows) { if ((w.getLine() == line) && (w.getStart() >= oldEndOffsetInLine)) { int distance = w.getStart() - oldEndOffsetInLine; - if (lineChange != 0 || newEndOffsetInLine <= w.getStart()) { + boolean coveredByPrevTW = false; + int prevIndex = w.getIndex() - 1; + if (StudyUtils.indexIsValid(prevIndex, taskWindows)) { + TaskWindow prevTW = taskWindows.get(prevIndex); + if (prevTW.getLine() == line) { + int endOffset = prevTW.getStart() + prevTW.getLength(); + if (endOffset >= newEndOffsetInLine) { + coveredByPrevTW = true; + } + } + } + if (lineChange != 0 || newEndOffsetInLine <= w.getStart() || coveredByPrevTW) { w.setStart(distance + newEndOffsetInLine); w.setLine(line + lineChange); } @@ -217,12 +228,33 @@ public class TaskFile implements Stateful{ public void navigateToFirstTaskWindow(@NotNull final Editor editor) { if (!taskWindows.isEmpty()) { TaskWindow firstTaskWindow = StudyUtils.getFirst(taskWindows); - mySelectedTaskWindow = firstTaskWindow; - LogicalPosition taskWindowStart = new LogicalPosition(firstTaskWindow.getLine(), firstTaskWindow.getStart()); - editor.getCaretModel().moveToLogicalPosition(taskWindowStart); - int startOffset = firstTaskWindow.getRealStartOffset(editor.getDocument()); - int endOffset = startOffset + firstTaskWindow.getLength(); - editor.getSelectionModel().setSelection(startOffset, endOffset); + navigateToTaskWindow(editor, firstTaskWindow); + } + } + + private void navigateToTaskWindow(@NotNull final Editor editor, @NotNull final TaskWindow firstTaskWindow) { + if (!firstTaskWindow.isValid(editor.getDocument())) { + return; } + mySelectedTaskWindow = firstTaskWindow; + LogicalPosition taskWindowStart = new LogicalPosition(firstTaskWindow.getLine(), firstTaskWindow.getStart()); + editor.getCaretModel().moveToLogicalPosition(taskWindowStart); + int startOffset = firstTaskWindow.getRealStartOffset(editor.getDocument()); + int endOffset = startOffset + firstTaskWindow.getLength(); + editor.getSelectionModel().setSelection(startOffset, endOffset); + } + + public void navigateToFirstFailedTaskWindow(@NotNull final Editor editor) { + for (TaskWindow taskWindow : taskWindows) { + if (taskWindow.getStatus() != StudyStatus.Failed) { + continue; + } + navigateToTaskWindow(editor, taskWindow); + break; + } + } + + public boolean hasFailedTaskWindows() { + return taskWindows.size() > 0 && getStatus() == StudyStatus.Failed; } } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java index 4fb112cc1f9b..dc4a75a800ce 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java @@ -1,5 +1,8 @@ package com.jetbrains.python.edu.course; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.EditorColors; @@ -8,16 +11,27 @@ 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.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.JBColor; +import com.jetbrains.python.edu.StudyDocumentListener; +import com.jetbrains.python.edu.StudyTestRunner; +import com.jetbrains.python.edu.StudyUtils; import org.jetbrains.annotations.NotNull; +import java.io.File; +import java.io.IOException; + /** * Implementation of windows which user should type in */ public class TaskWindow implements Comparable, Stateful { - + private static final String WINDOW_POSTFIX = "_window.py"; + private static final Logger LOG = Logger.getInstance(TaskWindow.class); public int line = 0; public int start = 0; public String hint = ""; @@ -174,4 +188,55 @@ public class TaskWindow implements Comparable, Stateful { public int getIndex() { return myIndex; } + + public void smartCheck(@NotNull final Project project, + @NotNull final VirtualFile answerFile, + @NotNull final TaskFile answerTaskFile, + @NotNull final TaskFile usersTaskFile, + @NotNull final StudyTestRunner testRunner, + @NotNull final VirtualFile virtualFile, + @NotNull final Document usersDocument) { + + try { + VirtualFile windowCopy = + answerFile.copy(this, answerFile.getParent(), answerFile.getNameWithoutExtension() + WINDOW_POSTFIX); + final FileDocumentManager documentManager = FileDocumentManager.getInstance(); + final Document windowDocument = documentManager.getDocument(windowCopy); + if (windowDocument != null) { + File resourceFile = StudyUtils.copyResourceFile(virtualFile.getName(), windowCopy.getName(), project, usersTaskFile.getTask()); + TaskFile windowTaskFile = new TaskFile(); + TaskFile.copy(answerTaskFile, windowTaskFile); + StudyDocumentListener listener = new StudyDocumentListener(windowTaskFile); + windowDocument.addDocumentListener(listener); + int start = getRealStartOffset(windowDocument); + int end = start + getLength(); + TaskWindow userTaskWindow = usersTaskFile.getTaskWindows().get(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(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, StudyStatus.Unchecked); + StudyUtils.deleteFile(windowCopy); + StudyUtils.deleteFile(fileWindows); + if (!resourceFile.delete()) { + LOG.error("failed to delete", resourceFile.getPath()); + } + } + } + catch (ExecutionException e) { + LOG.error(e); + } + catch (IOException e) { + LOG.error(e); + } + } }
\ No newline at end of file diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java b/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java index 69c5acc5f127..6b27c4a406db 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java @@ -1,8 +1,10 @@ package com.jetbrains.python.edu.editor; import com.intellij.codeHighlighting.BackgroundEditorHighlighter; +import com.intellij.icons.AllIcons; import com.intellij.ide.structureView.StructureViewBuilder; import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorFactory; @@ -17,9 +19,15 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.pom.Navigatable; +import com.intellij.ui.BrowserHyperlinkListener; import com.intellij.ui.HideableTitledPanel; import com.intellij.ui.JBColor; +import com.intellij.util.ui.EmptyClipboardOwner; import com.intellij.util.ui.UIUtil; import com.jetbrains.python.edu.StudyDocumentListener; import com.jetbrains.python.edu.StudyTaskManager; @@ -36,8 +44,8 @@ import javax.swing.text.MutableAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.datatransfer.StringSelection; +import java.awt.event.*; import java.beans.PropertyChangeListener; import java.util.HashMap; import java.util.Map; @@ -50,12 +58,13 @@ public class StudyEditor implements TextEditor { private static final String TASK_TEXT_HEADER = "Task Text"; private final FileEditor myDefaultEditor; private final JComponent myComponent; + private final TaskFile myTaskFile; private JButton myCheckButton; private JButton myNextTaskButton; private JButton myPrevTaskButton; private JButton myRefreshButton; private static final Map<Document, StudyDocumentListener> myDocumentListeners = new HashMap<Document, StudyDocumentListener>(); - private Project myProject; + private final Project myProject; public JButton getCheckButton() { return myCheckButton; @@ -65,6 +74,10 @@ public class StudyEditor implements TextEditor { return myPrevTaskButton; } + public TaskFile getTaskFile() { + return myTaskFile; + } + private static JButton addButton(@NotNull final JComponent parentComponent, String toolTipText, Icon icon) { JButton newButton = new JButton(); newButton.setToolTipText(toolTipText); @@ -89,26 +102,72 @@ public class StudyEditor implements TextEditor { myComponent = myDefaultEditor.getComponent(); JPanel studyPanel = new JPanel(); studyPanel.setLayout(new BoxLayout(studyPanel, BoxLayout.Y_AXIS)); - TaskFile taskFile = StudyTaskManager.getInstance(myProject).getTaskFile(file); - if (taskFile != null) { - Task currentTask = taskFile.getTask(); + myTaskFile = StudyTaskManager.getInstance(myProject).getTaskFile(file); + if (myTaskFile != null) { + Task currentTask = myTaskFile.getTask(); String taskText = currentTask.getResourceText(project, currentTask.getText(), false); initializeTaskText(studyPanel, taskText); JPanel studyButtonPanel = new JPanel(new GridLayout(1, 2)); JPanel taskActionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); studyButtonPanel.add(taskActionsPanel); studyButtonPanel.add(new JPanel()); - initializeButtons(taskActionsPanel, taskFile); + initializeButtons(taskActionsPanel, myTaskFile); studyPanel.add(studyButtonPanel); myComponent.add(studyPanel, BorderLayout.NORTH); } } - private static void initializeTaskText(JPanel studyPanel, @Nullable String taskText) { + class CopyListener extends MouseAdapter { + final JTextPane myTextPane; + + public CopyListener(JTextPane textPane) { + myTextPane = textPane; + } + + @Override + public void mouseReleased(MouseEvent e) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + ToolWindow projectView = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.PROJECT_VIEW); + if (projectView == null) { + return; + } + final Component focusComponent = projectView.getComponent(); + IdeFocusManager.getInstance(myProject).requestFocus(focusComponent, true); + final String text = myTextPane.getSelectedText(); + if (text == null) { + return; + } + KeyAdapter keyAdapter = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent ev) { + if (ev.getKeyCode() == KeyEvent.VK_C + && ev.getModifiers() == InputEvent.CTRL_MASK) { + StringSelection selection = new StringSelection(text); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, EmptyClipboardOwner.INSTANCE); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + IdeFocusManager.getInstance(myProject).requestFocus(myDefaultEditor.getComponent(), true); + } + }); + } + } + }; + focusComponent.addKeyListener(keyAdapter); + } + }); + } + } + + private void initializeTaskText(JPanel studyPanel, @Nullable String taskText) { JTextPane taskTextPane = new JTextPane(); + taskTextPane.addMouseListener(new CopyListener(taskTextPane)); taskTextPane.setContentType("text/html"); taskTextPane.setEditable(false); taskTextPane.setText(taskText); + taskTextPane.addHyperlinkListener(new BrowserHyperlinkListener()); EditorColorsScheme editorColorsScheme = EditorColorsManager.getInstance().getGlobalScheme(); int fontSize = editorColorsScheme.getEditorFontSize(); String fontName = editorColorsScheme.getEditorFontName(); @@ -134,10 +193,10 @@ public class StudyEditor implements TextEditor { private void initializeButtons(@NotNull final JPanel taskActionsPanel, @NotNull final TaskFile taskFile) { myCheckButton = addButton(taskActionsPanel, "Check task", StudyIcons.Resolve); myPrevTaskButton = addButton(taskActionsPanel, "Prev Task", StudyIcons.Prev); - myNextTaskButton = addButton(taskActionsPanel, "Next Task", StudyIcons.Next); - myRefreshButton = addButton(taskActionsPanel, "Start task again", StudyIcons.Refresh24); + myNextTaskButton = addButton(taskActionsPanel, "Next Task", AllIcons.Actions.Forward); + myRefreshButton = addButton(taskActionsPanel, "Start task again", AllIcons.Actions.Refresh); if (!taskFile.getTask().getUserTests().isEmpty()) { - JButton runButton = addButton(taskActionsPanel, "Run", StudyIcons.Run); + JButton runButton = addButton(taskActionsPanel, "Run", AllIcons.General.Run); runButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -149,7 +208,8 @@ public class StudyEditor implements TextEditor { watchInputButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - StudyEditInputAction studyEditInputAction = (StudyEditInputAction)ActionManager.getInstance().getAction("WatchInputAction"); + StudyEditInputAction studyEditInputAction = + (StudyEditInputAction)ActionManager.getInstance().getAction("WatchInputAction"); studyEditInputAction.showInput(myProject); } }); @@ -165,7 +225,8 @@ public class StudyEditor implements TextEditor { myNextTaskButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - StudyNextStudyTaskAction studyNextTaskAction = (StudyNextStudyTaskAction)ActionManager.getInstance().getAction("NextTaskAction"); + StudyNextStudyTaskAction studyNextTaskAction = + (StudyNextStudyTaskAction)ActionManager.getInstance().getAction("NextTaskAction"); studyNextTaskAction.navigateTask(myProject); } }); @@ -180,7 +241,8 @@ public class StudyEditor implements TextEditor { myRefreshButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - StudyRefreshTaskAction studyRefreshTaskAction = (StudyRefreshTaskAction)ActionManager.getInstance().getAction("RefreshTaskAction"); + StudyRefreshTaskFileAction studyRefreshTaskAction = + (StudyRefreshTaskFileAction)ActionManager.getInstance().getAction("RefreshTaskAction"); studyRefreshTaskAction.refresh(myProject); } }); @@ -300,7 +362,8 @@ public class StudyEditor implements TextEditor { if (fileEditor instanceof StudyEditor) { return (StudyEditor)fileEditor; } - } catch (Exception e) { + } + catch (Exception e) { return null; } return null; @@ -325,8 +388,9 @@ public class StudyEditor implements TextEditor { @NotNull @Override public Editor getEditor() { - if (myDefaultEditor instanceof TextEditor) + if (myDefaultEditor instanceof TextEditor) { return ((TextEditor)myDefaultEditor).getEditor(); + } return EditorFactory.getInstance().createViewer(new DocumentImpl(""), myProject); } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/highlighting/StudyVisitorFilter.java b/python/edu/learn-python/src/com/jetbrains/python/edu/highlighting/StudyVisitorFilter.java new file mode 100644 index 000000000000..dc7495adb16e --- /dev/null +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/highlighting/StudyVisitorFilter.java @@ -0,0 +1,18 @@ +package com.jetbrains.python.edu.highlighting; + +import com.intellij.psi.PsiFile; +import com.jetbrains.python.edu.StudyTaskManager; +import com.jetbrains.python.inspections.PythonVisitorFilter; +import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection; +import org.jetbrains.annotations.NotNull; + +public class StudyVisitorFilter implements PythonVisitorFilter { + @Override + public boolean isSupported(@NotNull final Class visitorClass, @NotNull final PsiFile file) { + if (StudyTaskManager.getInstance(file.getProject()).getCourse() == null) return true; + if (visitorClass == PyUnresolvedReferencesInspection.class) { + return false; + } + return true; + } +} diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java index abf648c5c82a..2f80dba12695 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java @@ -32,7 +32,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode { @Override protected void updateImpl(PresentationData data) { - data.setIcon(StudyIcons.Unchecked); + data.setIcon(StudyIcons.Task); String valueName = myValue.getName(); StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(myProject); Course course = studyTaskManager.getCourse(); @@ -41,7 +41,7 @@ public class StudyDirectoryNode extends PsiDirectoryNode { } if (valueName.equals(myProject.getName())) { data.clearText(); - data.addText(course.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, JBColor.BLUE)); + data.addText(course.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, JBColor.BLACK)); data.addText(" (" + valueName + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); return; } @@ -91,15 +91,16 @@ public class StudyDirectoryNode extends PsiDirectoryNode { StudyStatus taskStatus = stateful.getStatus(); switch (taskStatus) { case Unchecked: { - updatePresentation(data, additionalName, JBColor.blue, StudyIcons.Unchecked); + updatePresentation(data, additionalName, JBColor.BLACK, stateful instanceof Lesson ? StudyIcons.Lesson : StudyIcons.Task); break; } case Solved: { - updatePresentation(data, additionalName, new JBColor(new Color(0, 134, 0), new Color(98, 150, 85)), StudyIcons.Checked); + updatePresentation(data, additionalName, new JBColor(new Color(0, 134, 0), new Color(98, 150, 85)), + stateful instanceof Lesson ? StudyIcons.LessonCompl : StudyIcons.TaskCompl); break; } case Failed: { - updatePresentation(data, additionalName, JBColor.RED, StudyIcons.Failed); + updatePresentation(data, additionalName, JBColor.RED, stateful instanceof Lesson ? StudyIcons.Lesson : StudyIcons.TaskProbl); } } } diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form index 133c38d4e8f8..8dd6506d710c 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form @@ -39,11 +39,9 @@ </component> </children> </grid> - <component id="6c40c" class="javax.swing.JLabel"> + <component id="6c40c" class="javax.swing.JLabel" binding="myLabel"> <constraints> - <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"> - <preferred-size width="81" height="-1"/> - </grid> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"/> </constraints> <properties> <font/> @@ -67,9 +65,7 @@ </component> <component id="f1e10" class="javax.swing.JButton" binding="myRefreshButton"> <constraints> - <grid row="0" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"> - <minimum-size width="30" height="23"/> - </grid> + <grid row="0" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/> </constraints> <properties> <hideActionText value="false"/> diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java index 0f1ec08a8856..6edad63586f0 100644 --- a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java +++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java @@ -2,6 +2,7 @@ package com.jetbrains.python.edu.ui; import com.intellij.facet.ui.FacetValidatorsManager; import com.intellij.facet.ui.ValidationResult; +import com.intellij.icons.AllIcons; import com.intellij.openapi.fileChooser.FileChooser; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.vfs.VirtualFile; @@ -9,7 +10,6 @@ import com.intellij.util.Consumer; import com.jetbrains.python.edu.StudyDirectoryProjectGenerator; import com.jetbrains.python.edu.StudyUtils; import com.jetbrains.python.edu.course.CourseInfo; -import icons.StudyIcons; import javax.swing.*; import java.awt.event.ActionEvent; @@ -32,6 +32,7 @@ public class StudyNewProjectPanel{ private JPanel myContentPanel; private JLabel myAuthorLabel; private JLabel myDescriptionLabel; + private JLabel myLabel; private final StudyDirectoryProjectGenerator myGenerator; private static final String CONNECTION_ERROR = "<html>Failed to download courses.<br>Check your Internet connection.</html>"; private static final String INVALID_COURSE = "Selected course is invalid"; @@ -56,7 +57,9 @@ public class StudyNewProjectPanel{ } initListeners(); myRefreshButton.setVisible(true); - myRefreshButton.setIcon(StudyIcons.Refresh); + myRefreshButton.setIcon(AllIcons.Actions.Refresh); + + myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize()); } private void initListeners() { diff --git a/python/edu/main_pycharm_edu.iml b/python/edu/main_pycharm_edu.iml index 12efe9b350d1..17f1d67a39da 100644 --- a/python/edu/main_pycharm_edu.iml +++ b/python/edu/main_pycharm_edu.iml @@ -16,6 +16,7 @@ <orderEntry type="module" module-name="ShortcutPromoter" /> <orderEntry type="module" module-name="python-educational" /> <orderEntry type="module" module-name="learn-python" /> + <orderEntry type="module" module-name="course-creator" /> </component> </module> diff --git a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml index 0fdf0d48fd08..eed232dcd3c7 100644 --- a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml +++ b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml @@ -19,5 +19,5 @@ <feedback eap-url="http://www.jetbrains.com/feedback/feedback.jsp?product=PyCharm&build=$BUILD&timezone=$TIMEZONE&eval=$EVAL" release-url="http://www.jetbrains.com/feedback/feedback.jsp?product=PyCharm&build=$BUILD&timezone=$TIMEZONE&eval=$EVAL"/> - <help file="pycharmhelp.jar" root="pycharm"/> + <help file="pycharm-eduhelp.jar" root="pycharm"/> </component> diff --git a/python/edu/src/META-INF/PyCharmEduPlugin.xml b/python/edu/src/META-INF/PyCharmEduPlugin.xml index d5b2fdfe8c28..87739a948968 100644 --- a/python/edu/src/META-INF/PyCharmEduPlugin.xml +++ b/python/edu/src/META-INF/PyCharmEduPlugin.xml @@ -19,6 +19,10 @@ </component> </application-components> + <extensions defaultExtensionNs="com.intellij"> + <codeInsight.lineMarkerProvider language="Python" implementationClass="com.jetbrains.python.edu.PyExecuteFileLineMarkerProvider"/> + </extensions> + <actions> <group overrides="true" class="com.intellij.openapi.actionSystem.EmptyActionGroup" id="ToolsMenu"/> @@ -38,5 +42,11 @@ <action overrides="true" class="com.intellij.openapi.actionSystem.EmptyAction" id="NewHtmlFile"/> + <group id="PyRunMenu"> + <action id="runCurrentFile" class="com.jetbrains.python.edu.PyRunCurrentFileAction"/> + <add-to-group group-id="RunMenu" anchor="first"/> + </group> + + </actions> </idea-plugin> diff --git a/python/edu/src/com/intellij/openapi/application/PyCharmEduConfigImportSettings.java b/python/edu/src/com/intellij/openapi/application/PyCharmEduConfigImportSettings.java new file mode 100644 index 000000000000..989c750681f3 --- /dev/null +++ b/python/edu/src/com/intellij/openapi/application/PyCharmEduConfigImportSettings.java @@ -0,0 +1,12 @@ +package com.intellij.openapi.application; + +import com.intellij.ide.plugins.PluginManagerCore; + +// see com.intellij.openapi.application.ConfigImportHelper.getConfigImportSettings +@SuppressWarnings("UnusedDeclaration") +public class PyCharmEduConfigImportSettings extends ConfigImportSettings { + public PyCharmEduConfigImportSettings() { + PluginManagerCore.disablePlugin("org.jetbrains.plugins.coursecreator"); + } + +} diff --git a/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java b/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java index ecf3d79a6424..2954f7c5b706 100644 --- a/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java +++ b/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java @@ -35,14 +35,15 @@ import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.keymap.Keymap; import com.intellij.openapi.keymap.ex.KeymapManagerEx; import com.intellij.openapi.keymap.impl.KeymapImpl; +import com.intellij.openapi.project.DumbAwareRunnable; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ProjectManagerAdapter; import com.intellij.openapi.project.ex.ProjectManagerEx; +import com.intellij.openapi.startup.StartupManager; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.wm.ToolWindowEP; -import com.intellij.openapi.wm.ToolWindowId; -import com.intellij.openapi.wm.WindowManager; +import com.intellij.openapi.wm.*; import com.intellij.platform.DirectoryProjectConfigurator; import com.intellij.platform.PlatformProjectViewOpener; import com.intellij.psi.codeStyle.CodeStyleSettings; @@ -62,9 +63,9 @@ import java.util.Set; */ @SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "UtilityClassWithPublicConstructor"}) public class PyCharmEduInitialConfigurator { - @NonNls private static final String DISPLAYED_PROPERTY = "PyCharm.initialConfigurationShown"; + @NonNls private static final String DISPLAYED_PROPERTY = "PyCharmEDU.initialConfigurationShown"; - @NonNls private static final String CONFIGURED = "PyCharm.InitialConfiguration"; + @NonNls private static final String CONFIGURED = "PyCharmEDU.InitialConfiguration"; public static class First { @@ -93,6 +94,8 @@ public class PyCharmEduInitialConfigurator { uiSettings.SHOW_MAIN_TOOLBAR = false; codeInsightSettings.REFORMAT_ON_PASTE = CodeInsightSettings.NO_REFORMAT; + Registry.get("ide.new.settings.dialog").setValue(true); + GeneralSettings.getInstance().setShowTipsOnStartup(false); EditorSettingsExternalizable.getInstance().setVirtualSpace(false); @@ -113,7 +116,7 @@ public class PyCharmEduInitialConfigurator { }); } }); - PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP = true; + PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP = false; } if (!propertiesComponent.isValueSet(DISPLAYED_PROPERTY)) { @@ -147,6 +150,29 @@ public class PyCharmEduInitialConfigurator { } patchProjectAreaExtensions(project); + + StartupManager.getInstance(project).runWhenProjectIsInitialized(new DumbAwareRunnable() { + @Override + public void run() { + if (project.isDisposed()) return; + + ToolWindowManager.getInstance(project).invokeLater(new Runnable() { + int count = 0; + + public void run() { + if (project.isDisposed()) return; + if (count++ < 3) { // we need to call this after ToolWindowManagerImpl.registerToolWindowsFromBeans + ToolWindowManager.getInstance(project).invokeLater(this); + return; + } + ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow("Project"); + if (toolWindow.getType() != ToolWindowType.SLIDING) { + toolWindow.activate(null); + } + } + }); + } + }); } }); } @@ -205,6 +231,11 @@ public class PyCharmEduInitialConfigurator { private static void showInitialConfigurationDialog() { final JFrame frame = WindowManager.getInstance().findVisibleFrame(); - new InitialConfigurationDialog(frame, "Python").show(); + new InitialConfigurationDialog(frame, "Python") { + @Override + protected boolean canCreateLauncherScript() { + return false; + } + }.show(); } } diff --git a/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java new file mode 100644 index 000000000000..03522bb8cf8a --- /dev/null +++ b/python/edu/src/com/jetbrains/python/edu/PyExecuteFileLineMarkerProvider.java @@ -0,0 +1,91 @@ +package com.jetbrains.python.edu; + +import com.intellij.codeHighlighting.Pass; +import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; +import com.intellij.codeInsight.daemon.LineMarkerInfo; +import com.intellij.codeInsight.daemon.LineMarkerProvider; +import com.intellij.execution.actions.ConfigurationContext; +import com.intellij.icons.AllIcons; +import com.intellij.ide.DataManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.markup.GutterIconRenderer; +import com.intellij.psi.PsiComment; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.util.Function; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyImportStatement; +import com.jetbrains.python.psi.PyStatement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.event.MouseEvent; +import java.util.Collection; +import java.util.List; + +/** + * @author traff + */ +public class PyExecuteFileLineMarkerProvider implements LineMarkerProvider { + @Nullable + @Override + public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) { + return null; + } + + @Override + public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) { + for (PsiElement element : elements) { + if (isFirstCodeLine(element)) { + result.add(new LineMarkerInfo<PsiElement>( + element, element.getTextRange(), AllIcons.Actions.Execute, Pass.UPDATE_OVERRIDEN_MARKERS, + new Function<PsiElement, String>() { + @Override + public String fun(PsiElement e) { + return "Execute '" + e.getContainingFile().getName() + "'"; + } + }, + new GutterIconNavigationHandler<PsiElement>() { + @Override + public void navigate(MouseEvent e, PsiElement elt) { + executeCurrentScript(elt); + } + }, + GutterIconRenderer.Alignment.RIGHT)); + } + } + } + + private static void executeCurrentScript(PsiElement elt) { + Editor editor = PsiUtilBase.findEditor(elt); + assert editor != null; + + final ConfigurationContext context = + ConfigurationContext.getFromContext(DataManager.getInstance().getDataContext(editor.getComponent())); + PyRunCurrentFileAction.run(context); + } + + private static boolean isFirstCodeLine(PsiElement element) { + return element instanceof PyStatement && + element.getParent() instanceof PyFile && + !isNothing(element) && + nothingBefore(element); + } + + private static boolean nothingBefore(PsiElement element) { + element = element.getPrevSibling(); + while (element != null) { + if (!isNothing(element)) { + return false; + } + element = element.getPrevSibling(); + } + + return true; + } + + private static boolean isNothing(PsiElement element) { + return (element instanceof PsiComment) || (element instanceof PyImportStatement) || (element instanceof PsiWhiteSpace); + } +} diff --git a/python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java b/python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java new file mode 100644 index 000000000000..4d30fa29a3b3 --- /dev/null +++ b/python/edu/src/com/jetbrains/python/edu/PyRunCurrentFileAction.java @@ -0,0 +1,56 @@ +package com.jetbrains.python.edu; + +import com.intellij.execution.Location; +import com.intellij.execution.RunManagerEx; +import com.intellij.execution.RunnerAndConfigurationSettings; +import com.intellij.execution.actions.ConfigurationContext; +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.execution.runners.ExecutionUtil; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; +import com.jetbrains.python.PythonFileType; +import org.jetbrains.annotations.NotNull; + +/** + * @author traff + */ +public class PyRunCurrentFileAction extends AnAction { + public PyRunCurrentFileAction() { + getTemplatePresentation().setIcon(AllIcons.Actions.Execute); + } + + @Override + public void update(AnActionEvent e) { + Presentation presentation = e.getPresentation(); + final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext()); + Location location = context.getLocation(); + if (location != null && location.getPsiElement().getContainingFile() != null && location.getPsiElement().getContainingFile().getFileType() == PythonFileType.INSTANCE) { + presentation.setEnabled(true); + presentation.setText("Run '" + location.getPsiElement().getContainingFile().getName() + "'"); + } + } + + @Override + public void actionPerformed(AnActionEvent e) { + final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext()); + + run(context); + } + + public static void run(@NotNull ConfigurationContext context) { + RunnerAndConfigurationSettings configuration = context.findExisting(); + final RunManagerEx runManager = (RunManagerEx)context.getRunManager(); + if (configuration == null) { + configuration = context.getConfiguration(); + if (configuration == null) { + return; + } + runManager.setTemporaryConfiguration(configuration); + } + runManager.setSelectedConfiguration(configuration); + + ExecutionUtil.runConfiguration(configuration, DefaultRunExecutor.getRunExecutorInstance()); + } +} |