Snapshot idea/138.1696 from git://git.jetbrains.org/idea/community.git

Change-Id: I50c97b83a815ce635e49a38380ba5b8765e4b16a
diff --git a/python/edu/build/build.xml b/python/edu/build/build.xml
deleted file mode 100644
index 913237c..0000000
--- a/python/edu/build/build.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<project name="PyCharm Educational Edition" default="all">
-  <property name="project.home" value="${basedir}/../../.."/>
-  <property name="python.home" value="${basedir}"/>
-  <property name="out.dir" value="${project.home}/out"/>
-  <property name="tmp.dir" value="${project.home}/out/tmp"/>
-
-  <target name="cleanup">
-    <delete dir="${out.dir}" failonerror="false"/>
-  </target>
-
-  <target name="init">
-    <mkdir dir="${out.dir}"/>
-    <mkdir dir="${tmp.dir}"/>
-  </target>
-
-  <macrodef name="call_gant">
-    <attribute name="script" />
-    <sequential>
-      <java failonerror="true" jar="${project.home}/lib/ant/lib/ant-launcher.jar" fork="true">
-        <jvmarg line="-Xmx612m -XX:MaxPermSize=152m -Didea.build.number=${idea.build.number} &quot;-DideaPath=${idea.path}&quot;"/>
-        <sysproperty key="java.awt.headless" value="true"/>
-        <arg line="&quot;-Dgant.script=@{script}&quot;"/>
-        <arg line="&quot;-Dteamcity.build.tempDir=${tmp.dir}&quot;"/>
-        <arg line="&quot;-Didea.build.number=${idea.build.number}&quot;"/>
-        <arg line="&quot;-Didea.test.group=ALL_EXCLUDE_DEFINED&quot;"/>
-        <arg value="-f"/>
-        <arg value="${project.home}/build/gant.xml"/>
-      </java>
-    </sequential>
-  </macrodef>
-
-  <target name="build" depends="init">
-    <call_gant script="${python.home}/pycharm_edu_build.gant"/>
-  </target>
-
-  <!--<target name="plugin" depends="init">-->
-    <!--<call_gant script="${python.home}/build/python_plugin_build.gant"/>-->
-  <!--</target>-->
-  <!--
-  <target name="test" depends="init">
-    <call_gant script="${project.home}/build/scripts/tests.gant"/>
-  </target>
-  -->
-
-  <target name="all" depends="cleanup,build"/>
-</project>
diff --git a/python/edu/build/desktop.ini b/python/edu/build/desktop.ini
new file mode 100644
index 0000000..f56d43c
--- /dev/null
+++ b/python/edu/build/desktop.ini
@@ -0,0 +1,100 @@
+[Settings]
+NumFields=6
+
+[Field 1]
+Type=checkbox
+Left=5
+Right=100
+Top=10
+Bottom=20
+State=0
+
+[Field 2]
+Type=checkbox
+Left=120
+Right=-1
+Top=10
+Bottom=20
+State=0
+
+[Field 3]
+Type=GroupBox
+Left=1
+Right=-1
+Top=35
+Bottom=65
+Text=Choice Python version
+
+[Field 4]
+Type=RadioButton
+Left=5
+Right=45
+Top=50
+Bottom=60
+State=1
+Text=Python 2
+
+[Field 5]
+Type=RadioButton
+Left=95
+Right=135
+Top=50
+Bottom=60
+State=0
+Text=Python 3
+
+[Field 6]
+Type=GroupBox
+Left=1
+Right=-1
+Top=75
+Bottom=105
+Text=Create Associations
+
+[Field 7]
+Type=checkbox
+Left=5
+Right=45
+Top=90
+Bottom=100
+State=0
+
+[Field 8]
+Type=checkbox
+Left=50
+Right=90
+Top=90
+Bottom=100
+State=0
+
+[Field 9]
+Type=checkbox
+Left=95
+Right=135
+Top=90
+Bottom=100
+State=0
+
+[Field 10]
+Type=checkbox
+Left=140
+Right=180
+Top=90
+Bottom=100
+State=0
+
+[Field 11]
+Type=checkbox
+Left=185
+Right=225
+Top=90
+Bottom=100
+State=0
+
+[Field 12]
+Type=checkbox
+Left=230
+Right=270
+Top=90
+Bottom=100
+State=0
diff --git a/python/edu/build/idea.nsi b/python/edu/build/idea.nsi
new file mode 100644
index 0000000..d9903c9
--- /dev/null
+++ b/python/edu/build/idea.nsi
@@ -0,0 +1,1228 @@
+!verbose 2
+
+!include "paths.nsi"
+!include "strings.nsi"
+!include "Registry.nsi"
+!include "version.nsi"
+
+; Product with version (IntelliJ IDEA #xxxx).
+
+; Used in registry to put each build info into the separate subkey
+; Add&Remove programs doesn't understand subkeys in the Uninstall key,
+; thus ${PRODUCT_WITH_VER} is used for uninstall registry information
+!define PRODUCT_REG_VER "${MUI_PRODUCT}\${VER_BUILD}"
+
+!define INSTALL_OPTION_ELEMENTS 7
+Name "${MUI_PRODUCT}"
+SetCompressor lzma
+; http://nsis.sourceforge.net/Shortcuts_removal_fails_on_Windows_Vista
+RequestExecutionLevel user
+
+;------------------------------------------------------------------------------
+; include "Modern User Interface"
+;------------------------------------------------------------------------------
+!include "MUI2.nsh"
+!include "FileFunc.nsh"
+!include UAC.nsh
+!include "InstallOptions.nsh"
+!include StrFunc.nsh
+!include LogicLib.nsh
+
+${UnStrStr}
+${UnStrLoc}
+${UnStrRep}
+${StrRep}
+
+ReserveFile "desktop.ini"
+ReserveFile "DeleteSettings.ini"
+ReserveFile '${NSISDIR}\Plugins\InstallOptions.dll'
+!insertmacro MUI_RESERVEFILE_LANGDLL
+
+!define MUI_ICON "${IMAGES_LOCATION}\${PRODUCT_ICON_FILE}"
+!define MUI_UNICON "${IMAGES_LOCATION}\${PRODUCT_UNINST_ICON_FILE}"
+
+!define MUI_HEADERIMAGE
+!define MUI_HEADERIMAGE_BITMAP "${IMAGES_LOCATION}\${PRODUCT_HEADER_FILE}"
+!define MUI_WELCOMEFINISHPAGE_BITMAP "${IMAGES_LOCATION}\${PRODUCT_LOGO_FILE}"
+
+;------------------------------------------------------------------------------
+; on GUI initialization installer checks whether IDEA is already installed
+;------------------------------------------------------------------------------
+
+!define MUI_CUSTOMFUNCTION_GUIINIT GUIInit
+
+Var baseRegKey
+Var IS_UPGRADE_60
+
+!define MUI_LANGDLL_REGISTRY_ROOT "HKCU" 
+!define MUI_LANGDLL_REGISTRY_KEY "Software\JetBrains\${MUI_PRODUCT}\${VER_BUILD}\" 
+!define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language"
+
+;check if the window is win7 or newer
+!macro INST_UNINST_SWITCH un
+  Function ${un}winVersion
+    ;The platform is returned into $0, minor version into $1.
+    ;Windows 7 is equals values of 6 as platform and 1 as minor version.
+    ;Windows 8 is equals values of 6 as platform and 2 as minor version.
+    nsisos::osversion
+    ${If} $0 == "6"
+      ${AndIf} $1 >= "1"
+      StrCpy $0 "1"
+    ${else}
+      StrCpy $0 "0"
+    ${EndIf}
+  FunctionEnd
+
+  Function ${un}compareFileInstallationTime
+    StrCpy $9 ""
+  get_first_file:
+    Pop $7
+    IfFileExists "$7" get_next_file 0
+      StrCmp $7 "Complete" complete get_first_file
+  get_next_file:
+    Pop $8
+    StrCmp $8 "Complete" 0 +2
+      ; check if there is only one property file
+      StrCmp $9 "no changes" complete different
+    IfFileExists "$8" 0 get_next_file
+    ClearErrors
+    ${GetTime} "$7" "M" $0 $1 $2 $3 $4 $5 $6
+    ${GetTime} "$8" "M" $R0 $R1 $R2 $R3 $R4 $R5 $R6
+    StrCmp $0 $R0 0 different
+      StrCmp $1 $R1 0 different
+        StrCmp $2 $R2 0 different
+          StrCmp $4 $R4 0 different
+            StrCmp $5 $R5 0 different
+              StrCmp $6 $R6 0 different
+		StrCpy $9 "no changes"
+		Goto get_next_file
+  different:
+    StrCpy $9 "Modified"
+  complete:
+FunctionEnd
+
+Function ${un}SplitStr
+Exch $0 ; str
+Push $1 ; inQ
+Push $3 ; idx
+Push $4 ; tmp
+StrCpy $1 0
+StrCpy $3 0
+loop:
+    StrCpy $4 $0 1 $3
+    ${If} $4 == '"'
+        ${If} $1 <> 0
+            StrCpy $0 $0 "" 1
+            IntOp $3 $3 - 1
+        ${EndIf}
+        IntOp $1 $1 !
+    ${EndIf}
+    ${If} $4 == '' ; The end?
+        StrCpy $1 0
+        StrCpy $4 ','
+    ${EndIf}
+    ${If} $4 == ','
+    ${AndIf} $1 = 0
+        StrCpy $4 $0 $3
+        StrCpy $1 $4 "" -1
+        ${IfThen} $1 == '"' ${|} StrCpy $4 $4 -1 ${|}
+        killspace:
+            IntOp $3 $3 + 1
+            StrCpy $0 $0 "" $3
+            StrCpy $1 $0 1
+            StrCpy $3 0
+            StrCmp $1 ',' killspace
+        Push $0 ; Remaining
+        Exch 4
+        Pop $0
+        StrCmp $4 "" 0 moreleft
+            Pop $4
+            Pop $3
+            Pop $1
+            Return
+        moreleft:
+        Exch $4
+        Exch 2
+        Pop $1
+        Pop $3
+        Return
+    ${EndIf}
+    IntOp $3 $3 + 1
+    Goto loop
+FunctionEnd
+
+!macroend
+!insertmacro INST_UNINST_SWITCH ""
+!insertmacro INST_UNINST_SWITCH "un."
+
+Function InstDirState
+	!define InstDirState `!insertmacro InstDirStateCall`
+
+	!macro InstDirStateCall _PATH _RESULT
+		Push `${_PATH}`
+		Call InstDirState
+		Pop ${_RESULT}
+	!macroend
+
+	Exch $0
+	Push $1
+	ClearErrors
+
+	FindFirst $1 $0 '$0\*.*'
+	IfErrors 0 +3
+	StrCpy $0 -1
+	goto end
+	StrCmp $0 '.' 0 +4
+	FindNext $1 $0
+	StrCmp $0 '..' 0 +2
+	FindNext $1 $0
+	FindClose $1
+	IfErrors 0 +3
+	StrCpy $0 0
+	goto end
+	StrCpy $0 1
+
+	end:
+	Pop $1
+	Exch $0
+FunctionEnd
+
+Function SplitFirstStrPart
+  Exch $R0
+  Exch
+  Exch $R1
+  Push $R2
+  Push $R3
+  StrCpy $R3 $R1
+  StrLen $R1 $R0
+  IntOp $R1 $R1 + 1
+  loop:
+    IntOp $R1 $R1 - 1
+    StrCpy $R2 $R0 1 -$R1
+    StrCmp $R1 0 exit0
+    StrCmp $R2 $R3 exit1 loop
+  exit0:
+  StrCpy $R1 ""
+  Goto exit2
+  exit1:
+    IntOp $R1 $R1 - 1
+    StrCmp $R1 0 0 +3
+     StrCpy $R2 ""
+     Goto +2
+    StrCpy $R2 $R0 "" -$R1
+    IntOp $R1 $R1 + 1
+    StrCpy $R0 $R0 -$R1
+    StrCpy $R1 $R2
+  exit2:
+  Pop $R3
+  Pop $R2
+  Exch $R1 ;rest
+  Exch
+  Exch $R0 ;first
+FunctionEnd
+
+Function VersionSplit
+    !define VersionSplit `!insertmacro VersionSplitCall`
+
+    !macro VersionSplitCall _FULL _PRODUCT _BRANCH _BUILD
+	Push `${_FULL}`
+	Call VersionSplit
+	Pop ${_PRODUCT}
+	Pop ${_BRANCH}
+	Pop ${_BUILD}
+    !macroend
+
+    Pop $R0
+    Push "-"
+    Push $R0
+    Call SplitFirstStrPart
+    Pop $R0
+    Pop $R1
+    Push "."
+    Push $R1
+    Call SplitFirstStrPart
+    Push $R0
+FunctionEnd
+
+Function OnDirectoryPageLeave
+    StrCpy $IS_UPGRADE_60 "0"
+    ${InstDirState} "$INSTDIR" $R0
+    IntCmp $R0 1 check_build skip_abort skip_abort
+check_build:
+    FileOpen $R1 "$INSTDIR\build.txt" "r"
+    IfErrors do_abort
+    FileRead $R1 $R2
+    FileClose $R1
+    IfErrors do_abort
+    ${VersionSplit} ${MIN_UPGRADE_BUILD} $R3 $R4 $R5
+    ${VersionSplit} ${MAX_UPGRADE_BUILD} $R6 $R7 $R8
+    ${VersionSplit} $R2 $R9 $R2 $R0
+    StrCmp $R9 $R3 0 do_abort
+    IntCmp $R2 $R4 0 do_abort
+    IntCmp $R0 $R5 do_accept do_abort
+
+    StrCmp $R9 $R6 0 do_abort
+    IntCmp $R2 $R7 0 0 do_abort
+    IntCmp $R0 $R8 do_abort do_accept do_abort
+
+do_accept:
+    StrCpy $IS_UPGRADE_60 "1"
+    FileClose $R1
+    Goto skip_abort
+
+do_abort:
+  ;check
+  ; - if there are no files into $INSTDIR (recursively) just excepted property files
+  ; - if property files have the same installation time.
+  StrCpy $9 "$INSTDIR"
+  Call instDirEmpty
+  StrCmp $9 "not empty" abort 0
+  Push "Complete"
+  Push "$INSTDIR\bin\${PRODUCT_EXE_FILE}.vmoptions"
+  Push "$INSTDIR\bin\idea.properties"
+  ${StrRep} $0 ${PRODUCT_EXE_FILE} ".exe" "64.exe.vmoptions"
+  Push "$INSTDIR\bin\$0"
+  Call compareFileInstallationTime
+  StrCmp $9 "Modified" abort skip_abort
+abort:
+  MessageBox MB_OK|MB_ICONEXCLAMATION "$(empty_or_upgrade_folder)"
+  Abort
+skip_abort:
+FunctionEnd
+
+
+;check if there are no files into $INSTDIR recursively just except property files.
+Function instDirEmpty
+  Push $0
+  Push $1
+  Push $2
+  ClearErrors
+  FindFirst $1 $2 "$9\*.*"
+nextElemement:
+  ;is the element a folder?
+  StrCmp $2 "." getNextElement
+  StrCmp $2 ".." getNextElement
+  IfFileExists "$9\$2\*.*" 0 nextFile
+    Push $9
+    StrCpy "$9" "$9\$2"
+    Call instDirEmpty
+    StrCmp $9 "not empty" done 0
+    Pop $9
+    Goto getNextElement
+nextFile:
+  ;is it the file property?
+  ${If} $2 != "idea.properties"
+    ${AndIf} $2 != "${PRODUCT_EXE_FILE}.vmoptions"
+      ${StrRep} $0 ${PRODUCT_EXE_FILE} ".exe" "64.exe.vmoptions"
+      ${AndIf} $2 != $0
+        StrCpy $9 "not empty"
+        Goto done
+  ${EndIf}
+getNextElement:
+  FindNext $1 $2
+  IfErrors 0 nextElemement
+done:
+  FindClose $1
+  Pop $2
+  Pop $1
+  Pop $0
+FunctionEnd
+
+
+;------------------------------------------------------------------------------
+; Variables
+;------------------------------------------------------------------------------
+  Var STARTMENU_FOLDER
+  Var config_path
+  Var system_path
+
+;------------------------------------------------------------------------------
+; configuration
+;------------------------------------------------------------------------------
+
+!insertmacro MUI_PAGE_WELCOME
+
+Page custom uninstallOldVersionDialog
+
+Var control_fields
+Var max_fields
+
+!ifdef LICENSE_FILE
+!insertmacro MUI_PAGE_LICENSE "$(myLicenseData)"
+!endif
+
+!define MUI_PAGE_CUSTOMFUNCTION_LEAVE OnDirectoryPageLeave
+!insertmacro MUI_PAGE_DIRECTORY
+
+Page custom ConfirmDesktopShortcut
+  !define MUI_STARTMENUPAGE_NODISABLE
+  !define MUI_STARTMENUPAGE_DEFAULTFOLDER "JetBrains"
+
+!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
+!define MUI_ABORTWARNING
+!insertmacro MUI_PAGE_INSTFILES
+!define MUI_FINISHPAGE_RUN_NOTCHECKED
+!define MUI_FINISHPAGE_RUN
+!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
+!insertmacro MUI_PAGE_FINISH
+
+!define MUI_UNINSTALLER
+;!insertmacro MUI_UNPAGE_CONFIRM
+UninstPage custom un.ConfirmDeleteSettings
+!insertmacro MUI_UNPAGE_INSTFILES
+
+OutFile "${OUT_DIR}\${OUT_FILE}.exe"
+
+InstallDir "$PROGRAMFILES\${MANUFACTURER}\${PRODUCT_WITH_VER}"
+!define MUI_BRANDINGTEXT " "
+BrandingText " "
+
+Function PageFinishRun
+!insertmacro UAC_AsUser_ExecShell "" "$INSTDIR\bin\${PRODUCT_EXE_FILE}" "" "" ""
+FunctionEnd
+
+;------------------------------------------------------------------------------
+; languages
+;------------------------------------------------------------------------------
+!insertmacro MUI_LANGUAGE "English"
+;!insertmacro MUI_LANGUAGE "Japanese"
+!include "idea_en.nsi"
+;!include "idea_jp.nsi"
+
+!ifdef LICENSE_FILE
+LicenseLangString myLicenseData ${LANG_ENGLISH} "${LICENSE_FILE}.txt"
+LicenseLangString myLicenseData ${LANG_JAPANESE} "${LICENSE_FILE}.txt"
+!endif
+
+Function .onInit
+  StrCpy $baseRegKey "HKCU"
+  IfSilent UAC_Done
+UAC_Elevate:
+    !insertmacro UAC_RunElevated
+    StrCmp 1223 $0 UAC_ElevationAborted ; UAC dialog aborted by user? - continue install under user
+    StrCmp 0 $0 0 UAC_Err ; Error?
+    StrCmp 1 $1 0 UAC_Success ;Are we the real deal or just the wrapper?
+    Quit
+UAC_Err:
+    Abort
+UAC_ElevationAborted:
+    StrCpy $INSTDIR "$APPDATA\${MANUFACTURER}\${PRODUCT_WITH_VER}"
+    goto UAC_Done
+UAC_Success:
+    StrCmp 1 $3 UAC_Admin ;Admin?
+    StrCmp 3 $1 0 UAC_ElevationAborted ;Try again?
+    goto UAC_Elevate
+UAC_Admin:
+    StrCpy $INSTDIR "$PROGRAMFILES\${MANUFACTURER}\${PRODUCT_WITH_VER}"
+    SetShellVarContext all
+    StrCpy $baseRegKey "HKLM"
+UAC_Done:	
+;  !insertmacro MUI_LANGDLL_DISPLAY
+FunctionEnd
+
+Function checkVersion
+  StrCpy $2 ""
+  StrCpy $1 "Software\${MANUFACTURER}\${PRODUCT_REG_VER}"
+;  ${If} $0 == "HKLM"
+;    StrCpy $1 "Software\${MANUFACTURER}\${PRODUCT_REG_VER}"
+;    Push $0
+;    call winVersion
+;    ${If} $0 == "1"
+;      StrCpy $1 "Software\Wow6432Node\${MANUFACTURER}\${PRODUCT_REG_VER}"
+;    ${Else}
+;      StrCpy $1 "Software\${MANUFACTURER}\${PRODUCT_REG_VER}"
+;    ${EndIf}
+;    Pop $0
+;  ${EndIf}
+  Call OMReadRegStr
+  IfFileExists $3\bin\${PRODUCT_EXE_FILE} check_version
+  Goto Done
+check_version:
+  StrCpy $2 "Build"
+  Call OMReadRegStr
+  StrCmp $3 "" Done
+  IntCmpU $3 ${VER_BUILD} ask_Install_Over Done ask_Install_Over
+ask_Install_Over:
+  MessageBox MB_YESNO|MB_ICONQUESTION "$(current_version_already_installed)" IDYES continue IDNO exit_installer
+exit_installer:
+  Abort
+continue:
+  StrCpy $0 "complete"
+Done:
+FunctionEnd
+
+
+Function searchCurrentVersion
+  ; search current version of IDEA
+  StrCpy $0 "HKCU"
+  Call checkVersion
+  StrCmp $0 "complete" Done
+  StrCpy $0 "HKLM"
+  Call checkVersion
+Done:  
+FunctionEnd
+
+
+Function uninstallOldVersion
+  ;check if the uninstalled application is running
+remove_previous_installation:
+  ;prepare a copy of launcher
+  CopyFiles "$3\bin\${PRODUCT_EXE_FILE}" "$3\bin\${PRODUCT_EXE_FILE}_copy"
+  ClearErrors
+  ;copy launcher to itself
+  CopyFiles "$3\bin\${PRODUCT_EXE_FILE}_copy" "$3\bin\${PRODUCT_EXE_FILE}"
+  Delete "$3\bin\${PRODUCT_EXE_FILE}_copy"
+  IfErrors 0 +3
+  MessageBox MB_OKCANCEL|MB_ICONQUESTION|MB_TOPMOST "$(application_running)" IDOK remove_previous_installation IDCANCEL complete
+  goto complete
+  ; uninstallation mode
+  !insertmacro INSTALLOPTIONS_READ $9 "UninstallOldVersions.ini" "Field 2" "State"
+  ${If} $9 == "1"
+    ExecWait '"$3\bin\Uninstall.exe" /S'
+  ${else}
+    ExecWait '"$3\bin\Uninstall.exe" _?=$3\bin'
+  ${EndIf}
+  IfFileExists $3\bin\${PRODUCT_EXE_FILE} 0 uninstall
+  goto complete
+uninstall:
+  ;previous installation has been removed
+  ;customer decided to keep properties?
+  IfFileExists $3\bin\idea.properties saveProperties fullRemove
+saveProperties:
+  Delete "$3\bin\Uninstall.exe"
+  Goto complete
+fullRemove:
+  RmDir /r "$3"
+complete:
+FunctionEnd
+
+
+Function checkProductVersion
+;$8 - count of already added fields to the dialog
+;$3 - an old version which will be checked if the one should be added too
+StrCpy $7 $control_fields
+StrCpy $6 ""
+loop:
+  IntOp $7 $7 + 1
+  ${If} $8 >= $7
+	!insertmacro INSTALLOPTIONS_READ $6 "UninstallOldVersions.ini" "Field $7" "Text"
+	${If} $6 == $3
+		;found the same value in list of installations
+		StrCpy $6 "duplicated"
+		Goto finish
+	${EndIf}
+    Goto loop
+  ${EndIf}
+finish:
+FunctionEnd
+
+
+Function uninstallOldVersionDialog
+  StrCpy $control_fields 2
+  StrCpy $max_fields 13
+  StrCpy $0 "HKLM"
+  StrCpy $4 0
+  ReserveFile "UninstallOldVersions.ini"
+  !insertmacro INSTALLOPTIONS_EXTRACT "UninstallOldVersions.ini"
+  StrCpy $8 $control_fields
+
+get_installation_info:
+  StrCpy $1 "Software\${MANUFACTURER}\${MUI_PRODUCT}"
+  StrCpy $5 "\bin\${PRODUCT_EXE_FILE}"
+  StrCpy $2 ""
+  Call getInstallationPath
+  StrCmp $3 "complete" next_registry_root
+  ;check if the old installation could be uninstalled
+  IfFileExists $3\bin\Uninstall.exe uninstall_dialog get_next_key
+uninstall_dialog:
+  Call checkProductVersion
+  ${If} $6 != "duplicated"
+    IntOp $8 $8 + 1
+    !insertmacro INSTALLOPTIONS_WRITE "UninstallOldVersions.ini" "Field $8" "Text" "$3"
+    StrCmp $8 $max_fields complete
+  ${EndIf}
+get_next_key:
+  IntOp $4 $4 + 1 ;to check next record from registry
+  goto get_installation_info
+
+next_registry_root:
+${If} $0 == "HKLM"
+  StrCpy $0 "HKCU"
+  StrCpy $4 0
+  Goto get_installation_info
+${EndIf}
+complete:
+!insertmacro INSTALLOPTIONS_WRITE "UninstallOldVersions.ini" "Settings" "NumFields" "$8"
+${If} $8 > $control_fields
+  ;$2 used in prompt text
+  StrCpy $2 "s"
+  StrCpy $7 $control_fields
+  IntOp $7 $7 + 1
+  StrCmp $8 $7 0 +2
+    StrCpy $2 ""
+  !insertmacro MUI_HEADER_TEXT "$(uninstall_previous_installations_title)" "$(uninstall_previous_installations)"
+  !insertmacro INSTALLOPTIONS_WRITE "UninstallOldVersions.ini" "Field 1" "Text" "$(uninstall_previous_installations_prompt)"
+  !insertmacro INSTALLOPTIONS_WRITE "UninstallOldVersions.ini" "Field 3" "Flags" "FOCUS"
+  !insertmacro INSTALLOPTIONS_DISPLAY "UninstallOldVersions.ini"
+  ;uninstall chosen installation(s)
+
+  ;no disabled controls. StrCmp $2 "OK" loop finish
+loop:
+  !insertmacro INSTALLOPTIONS_READ $0 "UninstallOldVersions.ini" "Field $8" "State"
+  !insertmacro INSTALLOPTIONS_READ $3 "UninstallOldVersions.ini" "Field $8" "Text"
+  ${If} $0 == "1"
+    Call uninstallOldVersion
+    ${EndIf}
+    IntOp $8 $8 - 1
+    StrCmp $8 $control_fields finish loop
+  ${EndIf}
+finish:
+FunctionEnd
+
+
+Function getInstallationPath
+  Push $1
+  Push $2
+  Push $5
+loop:
+  Call OMEnumRegKey
+  StrCmp $3 "" 0 getPath
+  StrCpy $3 "complete"
+  goto done
+getPath:
+  Push $1
+  StrCpy $1 "$1\$3"
+  Call OMReadRegStr
+  Pop $1
+  IfFileExists $3$5 done 0 
+  IntOp $4 $4 + 1
+  goto loop
+done:
+  Pop $5
+  Pop $2
+  Pop $1
+FunctionEnd
+
+
+Function GUIInit
+  Push $0
+  Push $1
+  Push $2
+  Push $3
+  Push $4
+  Push $5
+
+; is the current version of IDEA installed?
+  Call searchCurrentVersion
+
+; search old versions of IDEA
+  StrCpy $4 0
+  StrCpy $0 "HKCU"
+  StrCpy $1 "Software\${MANUFACTURER}\${MUI_PRODUCT}"
+  StrCpy $5 "\bin\${PRODUCT_EXE_FILE}"
+  StrCpy $2 ""
+  Call getInstallationPath
+  StrCmp $3 "complete" all_users
+  IfFileExists $3\bin\${PRODUCT_EXE_FILE} old_version_located all_users
+all_users:
+  StrCpy $4 0
+  StrCpy $0 "HKLM"
+  Call getInstallationPath
+  StrCmp $3 "complete" success
+  IfFileExists $3\bin\${PRODUCT_EXE_FILE} 0 success
+old_version_located:
+;  MessageBox MB_YESNO|MB_ICONQUESTION "$(previous_installations)" IDYES uninstall IDNO success
+;uninstall:
+;  Call uninstallOldVersions
+
+success:
+  IntCmp ${SHOULD_SET_DEFAULT_INSTDIR} 0 end_enum_versions_hklm
+  StrCpy $3 "0"        # latest build number
+  StrCpy $0 "0"        # registry key index
+
+enum_versions_hkcu:
+  EnumRegKey $1 "HKCU" "Software\${MANUFACTURER}\${MUI_PRODUCT}" $0
+  StrCmp $1 "" end_enum_versions_hkcu
+  IntCmp $1 $3 continue_enum_versions_hkcu continue_enum_versions_hkcu
+  StrCpy $3 $1
+  ReadRegStr $INSTDIR "HKCU" "Software\${MANUFACTURER}\${MUI_PRODUCT}\$3" ""
+  
+continue_enum_versions_hkcu:
+  IntOp $0 $0 + 1
+  Goto enum_versions_hkcu
+  
+end_enum_versions_hkcu:  
+
+  StrCpy $0 "0"        # registry key index
+
+enum_versions_hklm:
+  EnumRegKey $1 "HKLM" "Software\${MANUFACTURER}\${MUI_PRODUCT}" $0
+  StrCmp $1 "" end_enum_versions_hklm
+  IntCmp $1 $3 continue_enum_versions_hklm continue_enum_versions_hklm
+  StrCpy $3 $1
+  ReadRegStr $INSTDIR "HKLM" "Software\${MANUFACTURER}\${MUI_PRODUCT}\$3" ""
+  
+continue_enum_versions_hklm:
+  IntOp $0 $0 + 1
+  Goto enum_versions_hklm
+  
+end_enum_versions_hklm:
+
+  StrCmp $INSTDIR "" 0 skip_default_instdir
+  StrCpy $INSTDIR "$PROGRAMFILES\${MANUFACTURER}\${MUI_PRODUCT} ${MUI_VERSION_MAJOR}.${MUI_VERSION_MINOR}"
+skip_default_instdir:
+
+  Pop $5
+  Pop $4
+  Pop $3
+  Pop $2
+  Pop $1
+  Pop $0
+  !insertmacro INSTALLOPTIONS_EXTRACT "Desktop.ini"
+
+FunctionEnd
+
+Function DoAssociation
+ ; back up old value of an association
+ ReadRegStr $1 HKCR $R4 ""
+  StrCmp $1 "" skip_backup
+    StrCmp $1 ${PRODUCT_PATHS_SELECTOR} skip_backup
+    WriteRegStr HKCR $R4 "backup_val" $1
+skip_backup:
+  WriteRegStr HKCR $R4 "" "${PRODUCT_PATHS_SELECTOR}"
+  ReadRegStr $0 HKCR ${PRODUCT_PATHS_SELECTOR} ""
+  StrCmp $0 "" 0 command_exists
+	WriteRegStr HKCR ${PRODUCT_PATHS_SELECTOR} "" "${PRODUCT_FULL_NAME}"
+	WriteRegStr HKCR "${PRODUCT_PATHS_SELECTOR}\shell" "" "open"
+	WriteRegStr HKCR "${PRODUCT_PATHS_SELECTOR}\DefaultIcon" "" "$INSTDIR\bin\${PRODUCT_EXE_FILE},0"
+command_exists:
+  WriteRegStr HKCR "${PRODUCT_PATHS_SELECTOR}\shell\open\command" "" \
+    '$INSTDIR\bin\${PRODUCT_EXE_FILE} "%1"'
+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
+
+  !insertmacro INSTALLOPTIONS_READ $R2 "Desktop.ini" "Field 4" "State"
+  StrCmp $R2 1 "" python3
+  StrCpy $R2 "2.7"
+  goto check_python
+python3:  
+  StrCpy $R2 "3.4"
+check_python:  
+  ReadRegStr $1 "HKCU" "Software\Python\PythonCore\$R2\InstallPath" $0
+  StrCmp $1 "" installation_for_all_users
+  goto verefy_python_launcher
+installation_for_all_users:
+  ReadRegStr $1 "HKLM" "Software\Python\PythonCore\$R2\InstallPath" $0
+  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"
+  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:  
+  Pop $0
+  ${If} $0 == "OK" 
+    ExecCmd::exec 'msiexec /i "$INSTDIR\python\python_$R2.msi" /quiet /qn /norestart /log "$INSTDIR\python\python_$R2_silent.log"'
+  ${EndIf}
+
+python_exists:  
+  !insertmacro INSTALLOPTIONS_READ $R2 "Desktop.ini" "Field 1" "State"
+  StrCmp $R2 1 "" skip_desktop_shortcut
+  CreateShortCut "$DESKTOP\${PRODUCT_FULL_NAME_WITH_VER}.lnk" \
+                 "$INSTDIR\bin\${PRODUCT_EXE_FILE}" "" "" "" SW_SHOWNORMAL
+
+skip_desktop_shortcut:
+  ; OS is not win7
+  Call winVersion
+  ${If} $0 == "0" 
+    !insertmacro INSTALLOPTIONS_READ $R2 "Desktop.ini" "Field 2" "State"
+    StrCmp $R2 1 "" skip_quicklaunch_shortcut
+    CreateShortCut "$QUICKLAUNCH\${PRODUCT_FULL_NAME_WITH_VER}.lnk" \
+                   "$INSTDIR\bin\${PRODUCT_EXE_FILE}" "" "" "" SW_SHOWNORMAL
+  ${EndIf}	
+skip_quicklaunch_shortcut:
+
+  !insertmacro INSTALLOPTIONS_READ $R1 "Desktop.ini" "Settings" "NumFields"
+  IntCmp $R1 ${INSTALL_OPTION_ELEMENTS} do_association done do_association
+do_association:
+  StrCpy $R2 ${INSTALL_OPTION_ELEMENTS}  
+get_user_choice:
+  !insertmacro INSTALLOPTIONS_READ $R3 "Desktop.ini" "Field $R2" "State"
+  StrCmp $R3 1 "" next_association
+  !insertmacro INSTALLOPTIONS_READ $R4 "Desktop.ini" "Field $R2" "Text"
+  call DoAssociation
+next_association:  
+  IntOp $R2 $R2 + 1
+  IntCmp $R1 $R2 get_user_choice done get_user_choice
+
+done:   
+!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+; $STARTMENU_FOLDER stores name of IDEA folder in Start Menu,
+; save it name in the "MenuFolder" RegValue
+  CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER"
+
+  CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\${PRODUCT_FULL_NAME_WITH_VER}.lnk" \
+                 "$INSTDIR\bin\${PRODUCT_EXE_FILE}" "" "" "" SW_SHOWNORMAL
+;  CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall ${PRODUCT_FULL_NAME_WITH_VER}.lnk" \
+;                 "$INSTDIR\bin\Uninstall.exe"
+  StrCpy $0 $baseRegKey
+  StrCpy $1 "Software\${MANUFACTURER}\${PRODUCT_REG_VER}"
+  StrCpy $2 "MenuFolder"
+  StrCpy $3 "$STARTMENU_FOLDER"
+  Call OMWriteRegStr
+!insertmacro MUI_STARTMENU_WRITE_END
+
+  StrCmp ${IPR} "false" skip_ipr
+
+  ; back up old value of .ipr
+!define Index "Line${__LINE__}"
+  ReadRegStr $1 HKCR ".ipr" ""
+  StrCmp $1 "" "${Index}-NoBackup"
+    StrCmp $1 "IntelliJIdeaProjectFile" "${Index}-NoBackup"
+    WriteRegStr HKCR ".ipr" "backup_val" $1
+"${Index}-NoBackup:"
+  WriteRegStr HKCR ".ipr" "" "IntelliJIdeaProjectFile"
+  ReadRegStr $0 HKCR "IntelliJIdeaProjectFile" ""
+  StrCmp $0 "" 0 "${Index}-Skip"
+	WriteRegStr HKCR "IntelliJIdeaProjectFile" "" "IntelliJ IDEA Project File"
+	WriteRegStr HKCR "IntelliJIdeaProjectFile\shell" "" "open"
+	WriteRegStr HKCR "IntelliJIdeaProjectFile\DefaultIcon" "" "$INSTDIR\bin\idea.exe,0"
+"${Index}-Skip:"
+  WriteRegStr HKCR "IntelliJIdeaProjectFile\shell\open\command" "" \
+    '$INSTDIR\bin\${PRODUCT_EXE_FILE} "%1"'
+!undef Index
+
+skip_ipr:
+
+; readonly section
+  SectionIn RO
+!include "idea_win.nsh"
+
+  IntCmp $IS_UPGRADE_60 1 skip_properties
+  SetOutPath $INSTDIR\bin
+  File "${PRODUCT_PROPERTIES_FILE}"
+  File "${PRODUCT_VM_OPTIONS_FILE}"
+skip_properties:
+
+  StrCpy $0 $baseRegKey
+  StrCpy $1 "Software\${MANUFACTURER}\${PRODUCT_REG_VER}"
+  StrCpy $2 ""
+  StrCpy $3 "$INSTDIR"
+  Call OMWriteRegStr
+  StrCpy $2 "Build"
+  StrCpy $3 ${VER_BUILD}
+  Call OMWriteRegStr
+
+; write uninstaller & add it to add/remove programs in control panel
+  WriteUninstaller "$INSTDIR\bin\Uninstall.exe"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" \
+            "DisplayName" "${PRODUCT_FULL_NAME_WITH_VER}"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" \
+              "UninstallString" "$INSTDIR\bin\Uninstall.exe"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" \
+              "InstallLocation" "$INSTDIR"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" \
+              "DisplayIcon" "$INSTDIR\bin\${PRODUCT_EXE_FILE}"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" \
+              "DisplayVersion" "${VER_BUILD}"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" \
+              "Publisher" "JetBrains s.r.o."
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" \
+              "URLInfoAbout" "http://www.jetbrains.com/products"
+  WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" \
+              "InstallType" "$baseRegKey"
+  WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" \
+              "NoModify" 1
+  WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}" \
+              "NoRepair" 1
+
+  ExecWait "$INSTDIR\jre\jre\bin\javaw.exe -Xshare:dump"
+  SetOutPath $INSTDIR\bin
+  ; set the current time for installation files under $INSTDIR\bin
+  ExecCmd::exec 'copy "$INSTDIR\bin\*.*s" +,,'
+  call winVersion
+  ${If} $0 == "1"  
+    ;ExecCmd::exec 'icacls "$INSTDIR" /grant %username%:F /T >"$INSTDIR"\installation_log.txt 2>"$INSTDIR"\installation_error.txt'
+    AccessControl::GrantOnFile \
+      "$INSTDIR" "(S-1-5-32-545)" "GenericRead + GenericExecute"
+  ${EndIf}
+SectionEnd
+
+;------------------------------------------------------------------------------
+; Descriptions of sections
+;------------------------------------------------------------------------------
+; LangString DESC_CopyRuntime ${LANG_ENGLISH} "${MUI_PRODUCT} files"
+
+;------------------------------------------------------------------------------
+; custom install pages
+;------------------------------------------------------------------------------
+
+Function ConfirmDesktopShortcut
+  !insertmacro MUI_HEADER_TEXT "$(installation_options)" "$(installation_options_prompt)"
+  !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 1" "Text" "$(create_desktop_shortcut)"
+  call winVersion
+  !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 2" "Text" "$(create_quick_launch_shortcut)"
+  ${If} $0 == "1"
+    !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field 2" "Flags" "DISABLED"
+  ${EndIf}
+  StrCmp "${ASSOCIATION}" "NoAssociation" skip_association
+  StrCpy $R0 6
+  push "${ASSOCIATION}"
+loop:
+  call SplitStr
+  Pop $0
+  StrCmp $0 "" done
+  IntOp $R0 $R0 + 1
+  !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Field $R0" "Text" "$0"
+  goto loop
+skip_association:
+  StrCpy $R0 2
+  call winVersion
+  ${If} $0 == "1"
+  IntOp $R0 $R0 - 1
+  ${EndIf}
+done:
+  !insertmacro INSTALLOPTIONS_WRITE "Desktop.ini" "Settings" "NumFields" "$R0"
+  !insertmacro INSTALLOPTIONS_DISPLAY "Desktop.ini"
+FunctionEnd
+
+
+;------------------------------------------------------------------------------
+; custom uninstall functions
+;------------------------------------------------------------------------------
+
+Function un.onInit
+  ;admin perm. is required to uninstall?
+  ${UnStrStr} $R0 $INSTDIR $PROGRAMFILES
+  StrCmp $R0 $INSTDIR requred_admin_perm UAC_Done
+
+requred_admin_perm:
+  ;the user has admin rights?
+  UserInfo::GetAccountType
+  Pop $R2
+  StrCmp $R2 "Admin" UAC_Admin uninstall_location
+
+uninstall_location:
+  ;check if the uninstallation is running from the product location
+  IfFileExists $APPDATA\${PRODUCT_PATHS_SELECTOR}_${VER_BUILD}_Uninstall.exe UAC_Elevate copy_uninstall
+
+copy_uninstall:
+  ;do copy for unistall.exe
+  CopyFiles "$OUTDIR\Uninstall.exe" "$APPDATA\${PRODUCT_PATHS_SELECTOR}_${VER_BUILD}_Uninstall.exe"
+  ExecWait '"$APPDATA\${PRODUCT_PATHS_SELECTOR}_${VER_BUILD}_Uninstall.exe" _?=$INSTDIR'
+  Delete "$APPDATA\${PRODUCT_PATHS_SELECTOR}_${VER_BUILD}_Uninstall.exe"
+  Quit
+
+UAC_Elevate:
+  !insertmacro UAC_RunElevated
+  StrCmp 1223 $0 UAC_ElevationAborted ; UAC dialog aborted by user? - continue install under user
+  StrCmp 0 $0 0 UAC_Err ; Error?
+  StrCmp 1 $1 0 UAC_Success ;Are we the real deal or just the wrapper?
+  Quit
+UAC_ElevationAborted:
+UAC_Err:
+  Abort
+UAC_Success:
+  StrCmp 1 $3 UAC_Admin ;Admin?
+  StrCmp 3 $1 0 UAC_ElevationAborted ;Try again?
+  goto UAC_Elevate
+UAC_Admin:
+  SetShellVarContext all
+  StrCpy $baseRegKey "HKLM"
+UAC_Done:
+  !insertmacro MUI_UNGETLANGUAGE
+  !insertmacro INSTALLOPTIONS_EXTRACT "DeleteSettings.ini"
+FunctionEnd
+
+Function OMEnumRegKey
+  StrCmp $0 "HKCU" hkcu
+    EnumRegKey $3 HKLM $1 $4
+    goto done
+hkcu:
+    EnumRegKey $3 HKCU $1 $4
+done:
+FunctionEnd
+
+Function un.OMReadRegStr
+  StrCmp $0 "HKCU" hkcu
+    ReadRegStr $3 HKLM $1 $2
+    goto done
+hkcu:
+    ReadRegStr $3 HKCU $1 $2
+done:
+FunctionEnd
+
+Function un.OMDeleteRegValue
+  StrCmp $0 "HKCU" hkcu
+    DeleteRegValue HKLM $1 $2
+    goto done
+hkcu:
+    DeleteRegValue HKCU $1 $2
+done:
+FunctionEnd
+
+Function un.ReturnBackupRegValue
+  ;replace Default str with the backup value (if there is the one) and then delete backup
+  ; $1 - key (for example ".java")
+  ; $2 - name (for example "backup_val")
+  Push $0
+  ReadRegStr $0 HKCR $1 $2
+  StrCmp $0 "" "noBackup"
+    WriteRegStr HKCR $1 "" $0
+    DeleteRegValue HKCR $1 $2
+noBackup:  
+  Pop $0
+FunctionEnd
+
+Function un.OMDeleteRegKeyIfEmpty
+  StrCmp $0 "HKCU" hkcu
+    DeleteRegKey /ifempty HKLM $1
+    goto done
+hkcu:
+    DeleteRegKey /ifempty HKCU $1
+done:
+FunctionEnd
+
+Function un.OMDeleteRegKey
+  StrCmp $0 "HKCU" hkcu
+    DeleteRegKey /ifempty HKLM $1
+    goto done
+hkcu:
+    DeleteRegKey /ifempty HKCU $1
+done:
+FunctionEnd
+
+Function un.OMWriteRegStr
+  StrCmp $0 "HKCU" hkcu
+    WriteRegStr HKLM $1 $2 $3
+    goto done
+hkcu:
+    WriteRegStr HKCU $1 $2 $3
+done:
+FunctionEnd
+
+
+;------------------------------------------------------------------------------
+; custom uninstall pages
+;------------------------------------------------------------------------------
+
+Function un.ConfirmDeleteSettings
+  !insertmacro MUI_HEADER_TEXT "$(uninstall_options)" "$(uninstall_options_prompt)"
+  !insertmacro INSTALLOPTIONS_WRITE "DeleteSettings.ini" "Field 1" "Text" "$(prompt_delete_settings)"
+  !insertmacro INSTALLOPTIONS_WRITE "DeleteSettings.ini" "Field 2" "Text" $INSTDIR
+  !insertmacro INSTALLOPTIONS_WRITE "DeleteSettings.ini" "Field 3" "Text" "$(text_delete_settings)"
+  !insertmacro INSTALLOPTIONS_WRITE "DeleteSettings.ini" "Field 4" "Text" "$(confirm_delete_caches)"
+  !insertmacro INSTALLOPTIONS_WRITE "DeleteSettings.ini" "Field 5" "Text" "$(confirm_delete_settings)"
+  !insertmacro INSTALLOPTIONS_DISPLAY "DeleteSettings.ini"
+FunctionEnd
+
+
+Function un.PrepareCustomPath
+  ;Input:
+  ;$0 - name of variable
+  ;$1 - value of the variable
+  ;$2 - line from the property file
+  push $3
+  push $5
+  ${UnStrLoc} $3 $2 $0 ">"
+  StrCmp $3 "" not_found
+  StrLen $5 $0
+  IntOp $3 $3 + $5
+  StrCpy $2 $2 "" $3
+  IfFileExists "$1$2\\*.*" not_found
+  StrCpy $2 $1$2
+  goto complete
+not_found:
+  StrCpy $0 ""
+complete:
+  pop $5
+  pop $3
+FunctionEnd
+
+
+Function un.getCustomPath
+  push $0
+  push $1
+  StrCpy $0 "${user.home}/"
+  StrCpy $1 "$PROFILE/"
+  Call un.PrepareCustomPath
+  StrCmp $0 "" check_idea_var
+  goto complete
+check_idea_var:
+  StrCpy $0 "${idea.home}/"
+  StrCpy $1 "$INSTDIR/"
+  Call un.PrepareCustomPath
+  StrCmp $2 "" +1 +2
+  StrCpy $2 ""
+complete:
+  pop $1
+  pop $0
+FunctionEnd
+
+
+Function un.getPath
+; The function read lines from idea.properties and search the substring and prepare the path to settings or caches.
+  ClearErrors
+  FileOpen $3 $INSTDIR\bin\idea.properties r
+  IfErrors complete ;file can not be open. not sure if a message should be displayed in this case.
+  StrLen $5 $1
+read_line:
+  FileRead $3 $4
+  StrCmp $4 "" complete
+  ${UnStrLoc} $6 $4 $1 ">"
+  StrCmp $6 "" read_line ; there is no substring in a string from the file. go for next one.
+  IntOp $6 $6 + $5
+  ${unStrStr} $7 $4 "#" ;check if the property has been customized
+  StrCmp $7 "" custom
+  StrCpy $2 "$PROFILE/${PRODUCT_SETTINGS_DIR}/$0" ;no. use the default value.
+  goto complete
+custom:
+  StrCpy $2 $4 "" $6
+  Call un.getCustomPath
+complete:
+  FileClose $3
+  ${UnStrRep} $2 $2 "/" "\"
+FunctionEnd
+
+
+Section "Uninstall"
+  StrCpy $baseRegKey "HKCU"
+  ; Uninstaller is in the \bin directory, we need upper level dir
+  StrCpy $INSTDIR $INSTDIR\..
+
+  !insertmacro INSTALLOPTIONS_READ $R2 "DeleteSettings.ini" "Field 4" "State"
+  DetailPrint "Data: $DOCUMENTS\..\${PRODUCT_SETTINGS_DIR}\"
+  StrCmp $R2 1 "" skip_delete_caches
+   ;find the path to caches (system) folder
+   StrCpy $0 "system"
+   StrCpy $1 "idea.system.path="
+   Call un.getPath
+   StrCmp $2 "" skip_delete_caches
+   StrCpy $system_path $2
+   RmDir /r "$system_path"
+   RmDir "$system_path\\.." ; remove parent of system dir if the dir is empty
+;   RmDir /r $DOCUMENTS\..\${PRODUCT_SETTINGS_DIR}\system
+skip_delete_caches:
+
+  !insertmacro INSTALLOPTIONS_READ $R3 "DeleteSettings.ini" "Field 5" "State"
+  StrCmp $R3 1 "" skip_delete_settings
+    ;find the path to settings (config) folder
+    StrCpy $0 "config"
+    StrCpy $1 "idea.config.path="
+    Call un.getPath
+    StrCmp $2 "" skip_delete_settings
+    StrCpy $config_path $2
+    RmDir /r "$config_path"
+;    RmDir /r $DOCUMENTS\..\${PRODUCT_SETTINGS_DIR}\config
+  Delete "$INSTDIR\bin\${PRODUCT_VM_OPTIONS_NAME}"
+  Delete "$INSTDIR\bin\idea.properties"
+  StrCmp $R2 1 "" skip_delete_settings
+  RmDir "$config_path\\.." ; remove parent of config dir if the dir is empty
+;    RmDir $DOCUMENTS\..\${PRODUCT_SETTINGS_DIR}
+skip_delete_settings:
+
+; Delete uninstaller itself
+  Delete "$INSTDIR\bin\Uninstall.exe"
+  Delete "$INSTDIR\jre\jre\bin\client\classes.jsa"
+
+  Push "Complete"
+  Push "$INSTDIR\bin\${PRODUCT_EXE_FILE}.vmoptions"
+  Push "$INSTDIR\bin\idea.properties"
+  ${UnStrRep} $0 ${PRODUCT_EXE_FILE} ".exe" "64.exe.vmoptions"
+  Push "$INSTDIR\bin\$0"
+  Call un.compareFileInstallationTime
+  ${If} $9 != "Modified"
+    RMDir /r "$INSTDIR"
+  ${Else}
+    !include "unidea_win.nsh"
+    RMDir "$INSTDIR"
+  ${EndIf}
+
+  ReadRegStr $R9 HKCU "Software\${MANUFACTURER}\${PRODUCT_REG_VER}" "MenuFolder"
+  StrCmp $R9 "" "" clear_shortcuts
+  ReadRegStr $R9 HKLM "Software\${MANUFACTURER}\${PRODUCT_REG_VER}" "MenuFolder"
+  StrCmp $R9 "" clear_Registry
+  StrCpy $baseRegKey "HKLM"
+  StrCpy $5 "Software\${MANUFACTURER}"
+;  call un.winVersion
+; ${If} $0 == "1"
+;    StrCpy $5 "Software\Wow6432Node\${MANUFACTURER}"
+; ${EndIf}
+clear_shortcuts:
+  ;the user has the admin rights
+;  UserInfo::GetAccountType
+;  Pop $R2
+  IfFileExists "$DESKTOP\${PRODUCT_FULL_NAME_WITH_VER}.lnk" keep_current_user
+  SetShellVarContext all
+keep_current_user:
+  DetailPrint "Start Menu: $SMPROGRAMS\$R9\${PRODUCT_FULL_NAME_WITH_VER}"
+ 
+  Delete "$SMPROGRAMS\$R9\${PRODUCT_FULL_NAME_WITH_VER}.lnk"
+;  Delete "$SMPROGRAMS\$R9\Uninstall ${PRODUCT_FULL_NAME_WITH_VER}.lnk"
+; Delete only if empty (last IDEA version is uninstalled)
+  RMDir  "$SMPROGRAMS\$R9"
+
+  Delete "$DESKTOP\${PRODUCT_FULL_NAME_WITH_VER}.lnk"
+  Delete "$QUICKLAUNCH\${PRODUCT_FULL_NAME_WITH_VER}.lnk"
+
+clear_Registry:
+  StrCpy $5 "Software\${MANUFACTURER}"
+; call un.winVersion
+; ${If} $0 == "1"
+;    StrCpy $5 "Software\Wow6432Node\${MANUFACTURER}"
+; ${EndIf}
+
+  StrCpy $0 $baseRegKey
+  StrCpy $1 "$5\${PRODUCT_REG_VER}"
+  StrCpy $2 "MenuFolder"
+  Call un.OMDeleteRegValue
+
+  StrCmp "${ASSOCIATION}" "NoAssociation" finish_uninstall
+  push "${ASSOCIATION}"
+loop:
+  call un.SplitStr
+  Pop $0
+  StrCmp $0 "" finish_uninstall
+  StrCpy $1 $0
+  StrCpy $2 "backup_val"
+  Call un.ReturnBackupRegValue
+  goto loop
+finish_uninstall:
+  StrCpy $1 "$5\${PRODUCT_REG_VER}"
+  StrCpy $2 "Build"
+  Call un.OMDeleteRegValue
+  StrCpy $2 ""
+  Call un.OMDeleteRegValue
+
+  StrCpy $1 "$5\${PRODUCT_REG_VER}"
+  Call un.OMDeleteRegKeyIfEmpty
+
+  StrCpy $1 "$5\${MUI_PRODUCT}"
+  Call un.OMDeleteRegKeyIfEmpty
+
+  StrCpy $1 "$5"
+  Call un.OMDeleteRegKeyIfEmpty
+
+  DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_WITH_VER}"
+
+; UNCOMMENT THIS IN RELEASE BUILD
+; ExecShell "" "http://www.jetbrains.com/idea/uninstall/"
+
+SectionEnd
diff --git a/python/edu/build/plugin-list.txt b/python/edu/build/plugin-list.txt
index ceaa608..bc4b94c 100644
--- a/python/edu/build/plugin-list.txt
+++ b/python/edu/build/plugin-list.txt
@@ -2,10 +2,6 @@
 git4idea
 remote-servers-git
 github
-terminal
 IntelliLang
-IntelliLang-xml
-IntelliLang-js
 IntelliLang-python
-rest
-python-rest
+learn-python
\ 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 8c857c1..2ef0bed 100644
--- a/python/edu/build/pycharm_edu_build.gant
+++ b/python/edu/build/pycharm_edu_build.gant
@@ -13,26 +13,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 import org.jetbrains.jps.LayoutInfo
 
-import static org.jetbrains.jps.idea.IdeaProjectLoader.guessHome
-
-setProperty("home", guessHome(this as Script))
-
-includeTargets << new File("${guessHome(this as Script)}/build/scripts/utils.gant")
-// signMacZip locates in ultimate_utils.gant
- includeTargets << new File("${guessHome(this)}/ultimate/build/scripts/ultimate_utils.gant")
-includeTargets << new File("${guessHome(this)}/build/scripts/libLicenses.gant")
+setProperty("home", getHome())
+includeTargets << new File("$home/community/build/scripts/utils.gant")
+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("ch", "$home")
+setProperty("buildName", "PE-$buildNumber")
+setProperty("ch", "$home/community")
 setProperty("pythonCommunityHome", "$ch/python")
 setProperty("pythonEduHome", "$ch/python/edu")
 
 // load ApplicationInfo.xml properties
 ant.xmlproperty(file: "$pythonEduHome/resources/idea/PyCharmEduApplicationInfo.xml", collapseAttributes: "true")
-
 setProperty("system_selector", "PyCharm${p("component.version.major")}0")
 setProperty("dryRun", false)
 setProperty("jdk16", guessJdk())
@@ -40,6 +36,12 @@
 //modules to compile
 setProperty("pluginFilter", new File("$pythonEduHome/build/plugin-list.txt").readLines())
 
+private String getHome(){
+  // current file is supposed to be at build/scripts/*.gant path
+  String uri = this["gant.file"]
+  return new File(new URI(uri).getSchemeSpecificPart()).getParentFile().getParentFile().getParentFile().getParentFile().getParentFile()
+}
+
 private List<String> pycharmPlatformApiModules() {
   return [platformApiModules, "dom-openapi"].flatten()
 }
@@ -93,6 +95,10 @@
 
   loadProject()
 
+  List resourcePaths = ["$ch/community-resources/src",
+                        "$ch/platform/icons/src",
+                        "$pythonEduHome/resources"]
+
   projectBuilder.stage("Cleaning up sandbox folder")
 
   projectBuilder.targetFolder = "${paths.sandbox}/classes"
@@ -126,6 +132,8 @@
     exclude(name: "intentionDescriptions")
     exclude(name: "tips/**/*")
     exclude(name: "tips")
+    exclude(name: "courses")
+    exclude(name: "courses/*")
   }
 
   zipSources(home, paths.artifacts)
@@ -135,13 +143,9 @@
   layoutEducational("${paths.sandbox}/classes/production", usedJars)
 
 
-  //buildNSIS([paths.distAll, paths.distWin],
-  //          "${pythonEduHome}/build/strings.nsi", "${pythonEduHome}/build/paths.nsi",
-  //          "pycharm", false, true, ".py", system_selector)
-
-  def extraArgs = ["build.code": "pycharmEDU-${buildNumber}", "build.number": "PE-$buildNumber", "artifacts.path": "${paths.artifacts}"]
-  signMacZip("pycharm", extraArgs)
-  buildDmg("pycharm", "${pythonEduHome}/build/DMG_background.png", extraArgs)
+  def extraArgs = ["build.code": "pycharm${buildName}", "build.number": "PE-$buildNumber", "artifacts.path": "${paths.artifacts}"]
+  signMacZip("pycharm", extraArgs + ["sitFileName": "pycharm${buildName}-jdk-bundled", "jdk_archive_name": "jdk_mac_redist_for_${buildNumber}.tar"])
+  buildDmg("pycharm", "${pythonEduHome}/build/DMG_background.png", extraArgs + ["sitFileName": "pycharm${buildName}-jdk-bundled", "jdk_archive_name": "jdk_mac_redist_for_${buildNumber}.tar"])
 
 }
 
@@ -152,7 +156,7 @@
     usedJars = collectUsedJars(modules(), approvedJars(), ["/ant/"], null)
   }
 
-  def appInfo = appInfoFile(classesPath)
+  def appInfo = appInfoFile()
   def paths = new Paths(home)
   buildSearchableOptions("${projectBuilder.moduleOutput(findModule("platform-resources"))}/search", [], {
     projectBuilder.moduleRuntimeClasspath(findModule("main_pycharm_edu"), false).each {
@@ -181,30 +185,42 @@
   def launcher = "${paths.distWin}/bin/pycharm.exe"
   List resourcePaths = ["$ch/community-resources/src",
     "$ch/platform/icons/src",
+    "$pythonCommunityHome/resources",
     "$pythonEduHome/resources"]
-  buildWinLauncher("$ch", "$ch/bin/WinLauncher/WinLauncher.exe", launcher,
+  buildWinLauncher(ch, "$ch/bin/WinLauncher/WinLauncher.exe", launcher,
                    appInfo, "$pythonEduHome/build/pycharm_edu_launcher.properties", system_selector, resourcePaths)
+  //signExe(launcher)
+  executeExternalAnt(["dirName": "${paths.distWin}/bin", "fileName": "pycharm.exe"], "$home/build/signBuild.xml")
 
-  buildWinZip("${paths.artifacts}/pycharmPE-${buildNumber}.zip", [paths.distAll, paths.distWin])
+  buildWinZip("${paths.artifacts}/pycharm${buildName}.zip", [paths.distAll, paths.distWin])
 
-  String tarRoot = isEap() ? "pycharm-edu-$buildNumber" : "pycharm-edu-${p("component.version.major")}.${p("component.version.minor")}"
-  buildTarGz(tarRoot, "$paths.artifacts/pycharmPE-${buildNumber}.tar", [paths.distAll, paths.distUnix])
+  buildNSIS([paths.distAll, paths.distWin],
+            "$pythonEduHome/build/strings.nsi", "$pythonEduHome/build/paths.nsi",
+            "pycharmPE-", false, true, ".py", system_selector)
 
-  String macAppRoot = isEap() ? "PyCharm EDU ${p("component.version.major")}.${p("component.version.minor")} EAP.app/Contents" : "PyCharm EDU.app/Contents"
-  buildMacZip(macAppRoot, "${paths.artifacts}/pycharmEDU-${buildNumber}.sit", [paths.distAll], paths.distMac)
+  String tarRoot = isEap() ? "pycharm-pe-$buildNumber" : "pycharm-pe-${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"
+  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")
 }
 
 private layoutPlugins(layouts) {
   dir("plugins") {
-    layouts.layoutPlugin("rest")
-    layouts.layoutPlugin("python-rest")
+    layouts.layoutPlugin("learn-python") {
+      dir("courses") {
+        fileset(dir: "$pythonEduHome/learn-python/resources/courses")
+      }
+    }
   }
 
   layouts.layoutCommunityPlugins(ch)
 }
 
-private String appInfoFile(String classesPath) {
-  return "$classesPath/python-educational/idea/PyCharmEduApplicationInfo.xml"
+private String appInfoFile() {
+  return "$pythonEduHome/resources/idea/PyCharmEduApplicationInfo.xml"
 }
 
 private layoutFull(Map args, String target, Set usedJars) {
diff --git a/python/edu/build/resources/logo.bmp b/python/edu/build/resources/logo.bmp
deleted file mode 100644
index 0f0f82d..0000000
--- a/python/edu/build/resources/logo.bmp
+++ /dev/null
Binary files differ
diff --git a/python/edu/build/resources/logo.png b/python/edu/build/resources/logo.png
new file mode 100644
index 0000000..1ccbc07
--- /dev/null
+++ b/python/edu/build/resources/logo.png
Binary files differ
diff --git a/python/edu/learn-python/gen/icons/StudyIcons.java b/python/edu/learn-python/gen/icons/StudyIcons.java
new file mode 100644
index 0000000..2840910
--- /dev/null
+++ b/python/edu/learn-python/gen/icons/StudyIcons.java
@@ -0,0 +1,29 @@
+package icons;
+
+import com.intellij.openapi.util.IconLoader;
+
+import javax.swing.*;
+
+/**
+ * NOTE THIS FILE IS AUTO-GENERATED
+ * DO NOT EDIT IT BY HAND, run build/scripts/icons.gant instead
+ */
+public class StudyIcons {
+  private static Icon load(String path) {
+    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 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 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
new file mode 100644
index 0000000..bd539d1
--- /dev/null
+++ b/python/edu/learn-python/learn-python.iml
@@ -0,0 +1,20 @@
+<?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$/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="platform-impl" />
+    <orderEntry type="module" module-name="python-community" />
+    <orderEntry type="module" module-name="lang-impl" />
+    <orderEntry type="library" name="gson" level="project" />
+    <orderEntry type="library" name="JUnit4" level="project" />
+  </component>
+</module>
+
diff --git a/python/edu/learn-python/resources/META-INF/plugin.xml b/python/edu/learn-python/resources/META-INF/plugin.xml
new file mode 100644
index 0000000..ec828eb
--- /dev/null
+++ b/python/edu/learn-python/resources/META-INF/plugin.xml
@@ -0,0 +1,73 @@
+<!--suppress XmlUnboundNsPrefix -->
+<idea-plugin version="2">
+  <id>com.jetbrains.python.edu.learn-python</id>
+  <name>Educational plugin for PyCharm</name>
+  <version>1.0</version>
+  <vendor email="support@yourcompany.com" url="http://www.yourcompany.com">YourCompany</vendor>
+
+  <description><![CDATA[
+
+      ]]></description>
+
+  <change-notes><![CDATA[
+
+      ]]>
+  </change-notes>
+
+  <!--depends>com.intellij.modules.python</depends-->
+
+  <!-- please see http://confluence.jetbrains.net/display/IDEADEV/Plugin+Compatibility+with+IntelliJ+Platform+Products
+       on how to target different products -->
+
+  <depends>com.intellij.modules.lang</depends>
+  <depends>com.intellij.modules.python</depends>
+  <application-components>
+  </application-components>
+
+  <project-components>
+    <component>
+      <implementation-class>com.jetbrains.python.edu.StudyTaskManager</implementation-class>
+      <interface-class>com.jetbrains.python.edu.StudyTaskManager</interface-class>
+    </component>
+  </project-components>
+
+  <application-components>
+    <component>
+      <implementation-class>com.jetbrains.python.edu.StudyInitialConfigurator</implementation-class>
+      <headless-implementation-class/>
+    </component>
+  </application-components>
+
+  <actions>
+    <action id="CheckAction" class="com.jetbrains.python.edu.actions.StudyCheckAction" text="check"
+            description="Runs tests for current tasks" icon="/icons/icon.jpg">
+    </action>
+    <action id="PrevWindowAction" class="com.jetbrains.python.edu.actions.StudyPrevWindowAction" text="PrevWindowAction" description="prev">
+    </action>
+
+    <action id="NextWindow" class="com.jetbrains.python.edu.actions.StudyNextWindowAction" text="NextWindowAction" description="next">
+    </action>
+    <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"
+            description="Refresh current task"/>
+    <action id="WatchInputAction" class="com.jetbrains.python.edu.actions.StudyEditInputAction" text="WatchInputAction"
+            description="watch input"/>
+    <action id="StudyRunAction" class="com.jetbrains.python.edu.actions.StudyRunAction" text="StudyRunAction" description="run your code"/>
+    <action id="ShowHintAction" class="com.jetbrains.python.edu.actions.StudyShowHintAction" text="Show hint"
+            description="show hint">
+      <add-to-group group-id="MainToolBar" anchor="last"/>
+    </action>
+  </actions>
+
+  <extensions defaultExtensionNs="com.intellij">
+    <toolWindow id="Course Description" anchor="right" factoryClass="com.jetbrains.python.edu.ui.StudyToolWindowFactory" conditionClass="com.jetbrains.python.edu.ui.StudyCondition"/>
+    <fileEditorProvider implementation="com.jetbrains.python.edu.editor.StudyFileEditorProvider"/>
+    <directoryProjectGenerator implementation="com.jetbrains.python.edu.StudyDirectoryProjectGenerator"/>
+    <treeStructureProvider implementation="com.jetbrains.python.edu.projectView.StudyTreeStructureProvider"/>
+    <highlightErrorFilter implementation="com.jetbrains.python.edu.StudyHighlightErrorFilter"/>
+    <applicationService serviceInterface="com.intellij.openapi.fileEditor.impl.EditorEmptyTextPainter"
+        serviceImplementation="com.jetbrains.python.edu.StudyInstructionPainter" overrides="true"/>
+  </extensions>
+</idea-plugin>
\ No newline at end of file
diff --git a/python/edu/learn-python/resources/com/jetbrains/python/edu/user_tester.py b/python/edu/learn-python/resources/com/jetbrains/python/edu/user_tester.py
new file mode 100644
index 0000000..c17e6cd
--- /dev/null
+++ b/python/edu/learn-python/resources/com/jetbrains/python/edu/user_tester.py
@@ -0,0 +1,58 @@
+import sys
+import imp
+import os
+import subprocess
+
+USER_TESTS = "userTests"
+
+TEST_FAILED = "FAILED"
+
+TEST_PASSED = "PASSED"
+
+INPUT = "input"
+OUTPUT = "output"
+
+
+def get_index(logical_name, full_name):
+    logical_name_len = len(logical_name)
+    if full_name[:logical_name_len] == logical_name:
+        return int(full_name[logical_name_len])
+    return -1
+
+
+def process_user_tests(file_path):
+    user_tests = []
+    imp.load_source('user_file', file_path)
+    user_tests_dir_path = os.path.abspath(os.path.join(file_path, os.pardir, USER_TESTS))
+    user_test_files = os.listdir(user_tests_dir_path)
+    for user_file in user_test_files:
+        index = get_index(INPUT, user_file)
+        if index == -1:
+            continue
+        output = OUTPUT + str(index)
+        if output in user_test_files:
+            input_path = os.path.abspath(os.path.join(user_tests_dir_path, user_file))
+            output_path = os.path.abspath(os.path.join(user_tests_dir_path, output))
+            user_tests.append((input_path, output_path, index))
+    return sorted(user_tests, key=(lambda x: x[2]))
+
+
+def run_user_test(python, executable_path):
+    user_tests = process_user_tests(executable_path)
+    for test in user_tests:
+        input, output, index = test
+        test_output = subprocess.check_output([python, executable_path, input])
+        expected_output = open(output).read()
+        test_status = TEST_PASSED if test_output == expected_output else TEST_FAILED
+        print "TEST" + str(index) + " " + test_status
+        print "OUTPUT:"
+        print test_output + "\n"
+        if test_status == TEST_FAILED:
+            print "EXPECTED OUTPUT:"
+            print expected_output + "\n"
+
+
+if __name__ == "__main__":
+    python = sys.argv[1]
+    executable_path = sys.argv[2]
+    run_user_test(python , executable_path)
\ No newline at end of file
diff --git a/python/edu/learn-python/resources/courses/introduction_course.zip b/python/edu/learn-python/resources/courses/introduction_course.zip
new file mode 100644
index 0000000..f3b24f2
--- /dev/null
+++ b/python/edu/learn-python/resources/courses/introduction_course.zip
Binary files differ
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
new file mode 100644
index 0000000..27a6e36
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/Run.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/WatchInput.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/WatchInput.png
new file mode 100644
index 0000000..4992191
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/WatchInput.png
Binary files differ
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
new file mode 100644
index 0000000..9494f2d
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/add.png
Binary files differ
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
new file mode 100644
index 0000000..4105a01
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/checked.png
Binary files differ
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
new file mode 100644
index 0000000..e2aaa55
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/failed.png
Binary files differ
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
new file mode 100644
index 0000000..3a9716e
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/icon.jpg
Binary files differ
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
new file mode 100644
index 0000000..dd1a5d9
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/next.png
Binary files differ
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
new file mode 100644
index 0000000..d12a751
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/playground.png
Binary files differ
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
new file mode 100644
index 0000000..0656f81
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/prev.png
Binary files differ
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
new file mode 100644
index 0000000..d595f6b
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh.png
Binary files differ
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
new file mode 100644
index 0000000..218f075
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/refresh24.png
Binary files differ
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
new file mode 100644
index 0000000..7ef960b
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve.png
Binary files differ
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
new file mode 100644
index 0000000..99aaa1d
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/resolve_dark.png
Binary files differ
diff --git a/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/showHint.png b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/showHint.png
new file mode 100644
index 0000000..f10fd56
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/showHint.png
Binary files differ
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
new file mode 100644
index 0000000..2145982
--- /dev/null
+++ b/python/edu/learn-python/resources/icons/com/jetbrains/python/edu/unchecked.png
Binary files differ
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
new file mode 100644
index 0000000..d4831d9
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java
@@ -0,0 +1,393 @@
+package com.jetbrains.python.edu;
+
+import com.google.gson.*;
+import com.google.gson.stream.JsonReader;
+import com.intellij.facet.ui.FacetEditorValidator;
+import com.intellij.facet.ui.FacetValidatorsManager;
+import com.intellij.facet.ui.ValidationResult;
+import com.intellij.lang.javascript.boilerplate.GithubDownloadUtil;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.platform.DirectoryProjectGenerator;
+import com.intellij.platform.templates.github.GeneratorException;
+import com.intellij.platform.templates.github.ZipUtil;
+import com.jetbrains.python.edu.course.Course;
+import com.jetbrains.python.edu.course.CourseInfo;
+import com.jetbrains.python.edu.ui.StudyNewProjectPanel;
+import com.jetbrains.python.newProject.PythonProjectGenerator;
+import icons.StudyIcons;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class StudyDirectoryProjectGenerator extends PythonProjectGenerator implements DirectoryProjectGenerator {
+  private static final Logger LOG = Logger.getInstance(StudyDirectoryProjectGenerator.class.getName());
+  private static final String REPO_URL = "https://github.com/JetBrains/pycharm-courses/archive/master.zip";
+  private static final String USER_NAME = "PyCharm";
+  private static final String COURSE_META_FILE = "course.json";
+  private static final String COURSE_NAME_ATTRIBUTE = "name";
+  private static final Pattern CACHE_PATTERN = Pattern.compile("(name=(.*)) (path=(.*course.json)) (author=(.*)) (description=(.*))");
+  private static final String REPOSITORY_NAME = "pycharm-courses";
+  public static final String AUTHOR_ATTRIBUTE = "author";
+  private final File myCoursesDir = new File(PathManager.getConfigPath(), "courses");
+  private static final String CACHE_NAME = "courseNames.txt";
+  private Map<CourseInfo, File> myCourses = new HashMap<CourseInfo, File>();
+  private File mySelectedCourseFile;
+  private Project myProject;
+  public ValidationResult myValidationResult = new ValidationResult("selected course is not valid");
+
+  @Nls
+  @NotNull
+  @Override
+  public String getName() {
+    return "Study project";
+  }
+
+
+  public void setCourses(Map<CourseInfo, File> courses) {
+    myCourses = courses;
+  }
+
+  /**
+   * Finds selected course in courses by name.
+   *
+   * @param courseName name of selected course
+   */
+  public void setSelectedCourse(@NotNull CourseInfo courseName) {
+    File courseFile = myCourses.get(courseName);
+    if (courseFile == null) {
+      LOG.error("invalid course in list");
+    }
+    mySelectedCourseFile = courseFile;
+  }
+
+  /**
+   * Adds course to courses specified in params
+   *
+   * @param courseDir must be directory containing course file
+   * @return added course name or null if course is invalid
+   */
+  @Nullable
+  private static CourseInfo addCourse(Map<CourseInfo, File> courses, File courseDir) {
+    if (courseDir.isDirectory()) {
+      File[] courseFiles = courseDir.listFiles(new FilenameFilter() {
+        @Override
+        public boolean accept(File dir, String name) {
+          return name.equals(COURSE_META_FILE);
+        }
+      });
+      if (courseFiles.length != 1) {
+        LOG.info("User tried to add course with more than one or without course files");
+        return null;
+      }
+      File courseFile = courseFiles[0];
+      CourseInfo courseInfo = getCourseInfo(courseFile);
+      if (courseInfo != null) {
+        courses.put(courseInfo, courseFile);
+      }
+      return courseInfo;
+    }
+    return null;
+  }
+
+
+  /**
+   * Adds course from zip archive to courses
+   *
+   * @return added course name or null if course is invalid
+   */
+  @Nullable
+  public CourseInfo addLocalCourse(String zipFilePath) {
+    File file = new File(zipFilePath);
+    try {
+      String fileName = file.getName();
+      String unzippedName = fileName.substring(0, fileName.indexOf("."));
+      File courseDir = new File(myCoursesDir, unzippedName);
+      ZipUtil.unzip(null, courseDir, file, null, null, true);
+      CourseInfo courseName = addCourse(myCourses, courseDir);
+      flushCache();
+      return courseName;
+    }
+    catch (IOException e) {
+      LOG.error("Failed to unzip course archive");
+      LOG.error(e);
+    }
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public Object showGenerationSettings(VirtualFile baseDir) throws ProcessCanceledException {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public Icon getLogo() {
+    return StudyIcons.Playground;
+  }
+
+
+  @Override
+  public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir,
+                              @Nullable Object settings, @NotNull Module module) {
+
+    myProject = project;
+    Reader reader = null;
+    try {
+      reader = new InputStreamReader(new FileInputStream(mySelectedCourseFile));
+      Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+      Course course = gson.fromJson(reader, Course.class);
+      course.init(false);
+      course.create(baseDir, new File(mySelectedCourseFile.getParent()));
+      course.setResourcePath(mySelectedCourseFile.getAbsolutePath());
+      VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
+      StudyTaskManager.getInstance(project).setCourse(course);
+    }
+    catch (FileNotFoundException e) {
+      LOG.error(e);
+    }
+    finally {
+      StudyUtils.closeSilently(reader);
+    }
+  }
+
+  /**
+   * Downloads courses from {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#REPO_URL}
+   * and unzips them into {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#myCoursesDir}
+   */
+
+  public void downloadAndUnzip(boolean needProgressBar) {
+    File outputFile = new File(PathManager.getConfigPath(), "courses.zip");
+    try {
+      if (!needProgressBar) {
+        GithubDownloadUtil.downloadAtomically(null, REPO_URL,
+                                              outputFile, USER_NAME, REPOSITORY_NAME);
+      }
+      else {
+        GithubDownloadUtil.downloadContentToFileWithProgressSynchronously(myProject, REPO_URL, "downloading courses", outputFile, USER_NAME,
+                                                                          REPOSITORY_NAME, false);
+      }
+      if (outputFile.exists()) {
+        ZipUtil.unzip(null, myCoursesDir, outputFile, null, null, true);
+        if (!outputFile.delete()) {
+          LOG.error("Failed to delete", outputFile.getName());
+        }
+        File[] files = myCoursesDir.listFiles();
+        if (files != null) {
+          for (File file : files) {
+            String fileName = file.getName();
+            if (StudyUtils.isZip(fileName)) {
+              ZipUtil.unzip(null, new File(myCoursesDir, fileName.substring(0, fileName.indexOf("."))), file, null, null, true);
+              if (!file.delete()) {
+                LOG.error("Failed to delete", fileName);
+              }
+            }
+          }
+        }
+      } else {
+        LOG.debug("failed to download course");
+      }
+    }
+    catch (IOException e) {
+      LOG.error(e);
+    }
+    catch (GeneratorException e) {
+      LOG.error(e);
+    }
+  }
+
+  public Map<CourseInfo, File> getLoadedCourses() {
+    return myCourses;
+  }
+
+  /**
+   * Parses courses located in {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#myCoursesDir}
+   * to {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#myCourses}
+   *
+   * @return map with course names and course files location
+   */
+  public Map<CourseInfo, File> loadCourses() {
+    Map<CourseInfo, File> courses = new HashMap<CourseInfo, File>();
+    if (myCoursesDir.exists()) {
+      File[] courseDirs = myCoursesDir.listFiles(new FileFilter() {
+        @Override
+        public boolean accept(File pathname) {
+          return pathname.isDirectory();
+        }
+      });
+      for (File courseDir : courseDirs) {
+        addCourse(courses, courseDir);
+      }
+    }
+    return courses;
+  }
+
+  /**
+   * Parses course json meta file and finds course name
+   *
+   * @return information about course or null if course file is invalid
+   */
+  @Nullable
+  private static CourseInfo getCourseInfo(File courseFile) {
+    CourseInfo courseInfo = null;
+    BufferedReader reader = null;
+    try {
+      if (courseFile.getName().equals(COURSE_META_FILE)) {
+        reader = new BufferedReader(new InputStreamReader(new FileInputStream(courseFile)));
+        JsonReader r = new JsonReader(reader);
+        JsonParser parser = new JsonParser();
+        JsonElement el = parser.parse(r);
+        String courseName = el.getAsJsonObject().get(COURSE_NAME_ATTRIBUTE).getAsString();
+        String courseAuthor = el.getAsJsonObject().get(AUTHOR_ATTRIBUTE).getAsString();
+        String courseDescription = el.getAsJsonObject().get("description").getAsString();
+        courseInfo = new CourseInfo(courseName, courseAuthor, courseDescription);
+      }
+    }
+    catch (Exception e) {
+      //error will be shown in UI
+    }
+    finally {
+      StudyUtils.closeSilently(reader);
+    }
+    return courseInfo;
+  }
+
+  @NotNull
+  @Override
+  public ValidationResult validate(@NotNull String s) {
+    return myValidationResult;
+  }
+
+  public void setValidationResult(ValidationResult validationResult) {
+    myValidationResult = validationResult;
+  }
+
+  /**
+   * @return courses from memory or from cash file or parses course directory
+   */
+  public Map<CourseInfo, File> getCourses() {
+    if (!myCourses.isEmpty()) {
+      return myCourses;
+    }
+    if (myCoursesDir.exists()) {
+      File cacheFile = new File(myCoursesDir, CACHE_NAME);
+      if (cacheFile.exists()) {
+        myCourses = getCoursesFromCache(cacheFile);
+        if (!myCourses.isEmpty()) {
+          return myCourses;
+        }
+      }
+      myCourses = loadCourses();
+      if (!myCourses.isEmpty()) {
+        return myCourses;
+      }
+    }
+    downloadAndUnzip(false);
+    myCourses = loadCourses();
+    flushCache();
+    return myCourses;
+  }
+
+  /**
+   * Writes courses to cash file {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#CACHE_NAME}
+   */
+  @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+  public void flushCache() {
+    File cashFile = new File(myCoursesDir, CACHE_NAME);
+    PrintWriter writer = null;
+    try {
+      if (!cashFile.exists()) {
+        final boolean created = cashFile.createNewFile();
+        if (!created) {
+          LOG.error("Cannot flush courses cache. Can't create " + CACHE_NAME + " file");
+          return;
+        }
+      }
+      writer = new PrintWriter(cashFile);
+      for (Map.Entry<CourseInfo, File> course : myCourses.entrySet()) {
+        CourseInfo courseInfo = course.getKey();
+        String line = String
+          .format("name=%s path=%s author=%s description=%s", courseInfo.getName(), course.getValue(), courseInfo.getAuthor(),
+                  courseInfo.getDescription());
+        writer.println(line);
+      }
+    }
+    catch (FileNotFoundException e) {
+      LOG.error(e);
+    }
+    catch (IOException e) {
+      LOG.error(e);
+    }
+    finally {
+      StudyUtils.closeSilently(writer);
+    }
+  }
+
+  /**
+   * Loads courses from {@link com.jetbrains.python.edu.StudyDirectoryProjectGenerator#CACHE_NAME} file
+   *
+   * @return map of course names and course files
+   */
+  @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+  private static Map<CourseInfo, File> getCoursesFromCache(File cashFile) {
+    Map<CourseInfo, File> coursesFromCash = new HashMap<CourseInfo, File>();
+    BufferedReader reader =  null;
+    try {
+       reader = new BufferedReader(new InputStreamReader(new FileInputStream(cashFile)));
+      String line;
+
+      while ((line = reader.readLine()) != null) {
+        Matcher matcher = CACHE_PATTERN.matcher(line);
+        if (matcher.matches()) {
+          String courseName = matcher.group(2);
+          File file = new File(matcher.group(4));
+          String author = matcher.group(6);
+          String description = matcher.group(8);
+          CourseInfo courseInfo = new CourseInfo(courseName, author, description);
+          if (file.exists()) {
+            coursesFromCash.put(courseInfo, file);
+          }
+        }
+      }
+    }
+    catch (FileNotFoundException e) {
+      LOG.error(e);
+    }
+    catch (IOException e) {
+      LOG.error(e);
+    }
+    finally {
+      StudyUtils.closeSilently(reader);
+    }
+    return coursesFromCash;
+  }
+
+  @Nullable
+  @Override
+  public JPanel extendBasePanel() throws ProcessCanceledException {
+    StudyNewProjectPanel settingsPanel = new StudyNewProjectPanel(this);
+    settingsPanel.registerValidators(new FacetValidatorsManager() {
+      public void registerValidator(FacetEditorValidator validator, JComponent... componentsToWatch) {
+        throw new UnsupportedOperationException();
+      }
+      public void validate() {
+        fireStateChanged();
+      }
+    });
+    return settingsPanel.getContentPanel();
+  }
+}
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
new file mode 100644
index 0000000..9fdcf70
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDocumentListener.java
@@ -0,0 +1,65 @@
+package com.jetbrains.python.edu;
+
+
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.editor.event.DocumentAdapter;
+import com.intellij.openapi.editor.event.DocumentEvent;
+import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.course.TaskWindow;
+
+/**
+ * author: liana
+ * data: 7/16/14.
+ * Listens changes in study files and updates
+ * coordinates of all the windows in current task file
+ */
+public class StudyDocumentListener extends DocumentAdapter {
+  private final TaskFile myTaskFile;
+  private int oldLine;
+  private int oldLineStartOffset;
+  private TaskWindow myTaskWindow;
+
+  public StudyDocumentListener(TaskFile taskFile) {
+    myTaskFile = taskFile;
+  }
+
+
+  //remembering old end before document change because of problems
+  // with fragments containing "\n"
+  @Override
+  public void beforeDocumentChange(DocumentEvent e) {
+    int offset = e.getOffset();
+    int oldEnd = offset + e.getOldLength();
+    Document document = e.getDocument();
+    oldLine = document.getLineNumber(oldEnd);
+    oldLineStartOffset = document.getLineStartOffset(oldLine);
+    int line = document.getLineNumber(offset);
+    int offsetInLine = offset - document.getLineStartOffset(line);
+    LogicalPosition pos = new LogicalPosition(line, offsetInLine);
+    myTaskWindow = myTaskFile.getTaskWindow(document, pos);
+
+  }
+
+  @Override
+  public void documentChanged(DocumentEvent e) {
+    if (e instanceof DocumentEventImpl) {
+      DocumentEventImpl event = (DocumentEventImpl)e;
+      Document document = e.getDocument();
+      int offset = e.getOffset();
+      int change = event.getNewLength() - event.getOldLength();
+      if (myTaskWindow != null) {
+        int newLength = myTaskWindow.getLength() + change;
+        myTaskWindow.setLength(newLength <= 0 ? 0 : newLength);
+      }
+      int newEnd = offset + event.getNewLength();
+      int newLine = document.getLineNumber(newEnd);
+      int lineChange = newLine - oldLine;
+      myTaskFile.incrementLines(oldLine + 1, lineChange);
+      int newEndOffsetInLine = offset + e.getNewLength() - document.getLineStartOffset(newLine);
+      int oldEndOffsetInLine = offset + e.getOldLength() - oldLineStartOffset;
+      myTaskFile.updateLine(lineChange, oldLine, newEndOffsetInLine, oldEndOffsetInLine);
+    }
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyEditorFactoryListener.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyEditorFactoryListener.java
new file mode 100644
index 0000000..7565d76
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyEditorFactoryListener.java
@@ -0,0 +1,100 @@
+package com.jetbrains.python.edu;
+
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.editor.event.EditorFactoryEvent;
+import com.intellij.openapi.editor.event.EditorFactoryListener;
+import com.intellij.openapi.editor.event.EditorMouseAdapter;
+import com.intellij.openapi.editor.event.EditorMouseEvent;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.python.edu.course.StudyStatus;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.course.TaskWindow;
+import com.jetbrains.python.edu.editor.StudyEditor;
+import org.jetbrains.annotations.NotNull;
+
+import java.awt.*;
+
+
+class StudyEditorFactoryListener implements EditorFactoryListener {
+
+  /**
+   * draws selected task window if there is one located in mouse position
+   */
+  private static class WindowSelectionListener extends EditorMouseAdapter {
+    private final TaskFile myTaskFile;
+
+    WindowSelectionListener(TaskFile taskFile) {
+      myTaskFile = taskFile;
+    }
+
+    @Override
+    public void mouseClicked(EditorMouseEvent e) {
+      Editor editor = e.getEditor();
+      Point point = e.getMouseEvent().getPoint();
+      LogicalPosition pos = editor.xyToLogicalPosition(point);
+      TaskWindow taskWindow = myTaskFile.getTaskWindow(editor.getDocument(), pos);
+      if (taskWindow != null) {
+        myTaskFile.setSelectedTaskWindow(taskWindow);
+        taskWindow.draw(editor, taskWindow.getStatus() != StudyStatus.Solved, true);
+      }
+      else {
+        myTaskFile.drawAllWindows(editor);
+      }
+    }
+  }
+
+  @Override
+  public void editorCreated(@NotNull final EditorFactoryEvent event) {
+    final Editor editor = event.getEditor();
+
+    final Project project = editor.getProject();
+    if (project == null) {
+      return;
+    }
+    ApplicationManager.getApplication().invokeLater(
+      new Runnable() {
+        @Override
+        public void run() {
+          ApplicationManager.getApplication().runWriteAction(new Runnable() {
+            @Override
+            public void run() {
+              Document document = editor.getDocument();
+              VirtualFile openedFile = FileDocumentManager.getInstance().getFile(document);
+              if (openedFile != null) {
+                StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+                TaskFile taskFile = taskManager.getTaskFile(openedFile);
+                if (taskFile != null) {
+                  taskFile.navigateToFirstTaskWindow(editor);
+                  editor.addEditorMouseListener(new WindowSelectionListener(taskFile));
+                  StudyDocumentListener listener = new StudyDocumentListener(taskFile);
+                  StudyEditor.addDocumentListener(document, listener);
+                  document.addDocumentListener(listener);
+                  taskFile.drawAllWindows(editor);
+                }
+              }
+            }
+          });
+        }
+      }
+    );
+  }
+
+  @Override
+  public void editorReleased(@NotNull EditorFactoryEvent event) {
+    Editor editor = event.getEditor();
+    Document document = editor.getDocument();
+    StudyDocumentListener listener = StudyEditor.getListener(document);
+    if (listener != null) {
+      document.removeDocumentListener(listener);
+      StudyEditor.removeListener(document);
+    }
+    editor.getMarkupModel().removeAllHighlighters();
+    editor.getSelectionModel().removeSelection();
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyHighlightErrorFilter.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyHighlightErrorFilter.java
new file mode 100644
index 0000000..1377128
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyHighlightErrorFilter.java
@@ -0,0 +1,23 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.codeInsight.highlighting.HighlightErrorFilter;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiErrorElement;
+import org.jetbrains.annotations.NotNull;
+import com.jetbrains.python.edu.course.TaskFile;
+
+/**
+ * author: liana
+ * data: 7/23/14.
+ */
+public class StudyHighlightErrorFilter extends HighlightErrorFilter {
+  @Override
+  public boolean shouldHighlightErrorElement(@NotNull final PsiErrorElement element) {
+    VirtualFile file = element.getContainingFile().getVirtualFile();
+    Project project = element.getProject();
+    StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+    TaskFile taskFile = taskManager.getTaskFile(file);
+    return taskFile == null;
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInitialConfigurator.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInitialConfigurator.java
new file mode 100644
index 0000000..34776f3
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInitialConfigurator.java
@@ -0,0 +1,67 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.codeInsight.CodeInsightSettings;
+import com.intellij.ide.RecentProjectsManagerBase;
+import com.intellij.ide.ui.UISettings;
+import com.intellij.ide.util.PropertiesComponent;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.project.ex.ProjectManagerEx;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.platform.templates.github.ZipUtil;
+import com.intellij.util.PathUtil;
+import com.intellij.util.messages.MessageBus;
+import org.jetbrains.annotations.NonNls;
+
+import java.io.File;
+import java.io.IOException;
+
+@SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "UtilityClassWithPublicConstructor"})
+public class StudyInitialConfigurator {
+  private static final Logger LOG = Logger.getInstance(StudyInitialConfigurator.class.getName()
+  );
+  @NonNls private static final String CONFIGURED = "StudyPyCharm.InitialConfiguration";
+
+
+  /**
+   * @noinspection UnusedParameters
+   */
+  public StudyInitialConfigurator(MessageBus bus,
+                                  UISettings uiSettings,
+                                  CodeInsightSettings codeInsightSettings,
+                                  final PropertiesComponent propertiesComponent,
+                                  FileTypeManager fileTypeManager,
+                                  final ProjectManagerEx projectManager,
+                                  RecentProjectsManagerBase recentProjectsManager) {
+    if (!propertiesComponent.getBoolean(CONFIGURED, false)) {
+      final File file = new File(getCoursesRoot(), "introduction_course.zip");
+      final File newCourses = new File(PathManager.getConfigPath(), "courses");
+      try {
+        FileUtil.createDirectory(newCourses);
+        String fileName = file.getName();
+        String unzippedName = fileName.substring(0, fileName.indexOf("."));
+        File courseDir = new File(newCourses, unzippedName);
+        ZipUtil.unzip(null, courseDir, file, null, null, true);
+
+      }
+      catch (IOException e) {
+        LOG.warn("Couldn't copy bundled courses " + e);
+      }
+    }
+  }
+
+  public static File getCoursesRoot() {
+    @NonNls String jarPath = PathUtil.getJarPathForClass(StudyInitialConfigurator.class);
+    if (jarPath.endsWith(".jar")) {
+      final File jarFile = new File(jarPath);
+
+
+      File pluginBaseDir = jarFile.getParentFile();
+      return new File(pluginBaseDir, "courses");
+    }
+
+    return new File(jarPath , "courses");
+  }
+
+}
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
new file mode 100644
index 0000000..4f34bfb
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyInstructionPainter.java
@@ -0,0 +1,47 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.openapi.fileEditor.impl.EditorEmptyTextPainter;
+import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
+import com.intellij.openapi.util.Couple;
+import com.intellij.ui.Gray;
+import com.intellij.ui.JBColor;
+import com.intellij.util.PairFunction;
+import com.intellij.util.ui.GraphicsUtil;
+import com.intellij.util.ui.UIUtil;
+import com.jetbrains.python.edu.ui.StudyCondition;
+
+import java.awt.*;
+
+/**
+ * author: liana
+ * data: 7/29/14.
+ */
+public class StudyInstructionPainter extends EditorEmptyTextPainter {
+  @Override
+  public void paintEmptyText(final EditorsSplitters splitters, Graphics g) {
+    if (!StudyCondition.VALUE) {
+      super.paintEmptyText(splitters, g);
+      return;
+    }
+    boolean isDarkBackground = UIUtil.isUnderDarcula();
+    UIUtil.applyRenderingHints(g);
+    GraphicsUtil.setupAntialiasing(g, true, false);
+    g.setColor(new JBColor(isDarkBackground ? Gray._230 : Gray._80, Gray._160));
+    g.setFont(UIUtil.getLabelFont().deriveFont(isDarkBackground ? 24f : 20f));
+
+    UIUtil.TextPainter painter = new UIUtil.TextPainter().withLineSpacing(1.5f);
+
+    painter.appendLine("PyCharm Educational Edition").underlined(new JBColor(Gray._150, Gray._180));
+    painter.appendLine("Navigate to the next task window with Ctrl + Enter").smaller().withBullet();
+    painter.appendLine("Navigate between task windows with Ctrl + < and Ctrl + >").smaller().withBullet();
+    painter.appendLine("Get hint for the task window using Ctrl + 7").smaller().withBullet();
+    painter.appendLine("To see your progress open the 'Course Description' panel").smaller().withBullet();
+                       painter.draw(g, new PairFunction<Integer, Integer, Couple<Integer>>() {
+                         @Override
+                         public Couple<Integer> fun(Integer width, Integer height) {
+                           Dimension s = splitters.getSize();
+                           return Couple.of((s.width - width) / 2, (s.height - height) / 2);
+                         }
+                       });
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyResourceManger.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyResourceManger.java
new file mode 100644
index 0000000..38f1c2f
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyResourceManger.java
@@ -0,0 +1,5 @@
+package com.jetbrains.python.edu;
+
+public interface StudyResourceManger {
+  String USER_TESTER = "user_tester.py";
+}
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
new file mode 100644
index 0000000..213c1f7
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTaskManager.java
@@ -0,0 +1,296 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.ide.ui.UISettings;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.actionSystem.ex.AnActionListener;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.keymap.Keymap;
+import com.intellij.openapi.keymap.KeymapManager;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.DumbAwareRunnable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.startup.StartupManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileAdapter;
+import com.intellij.openapi.vfs.VirtualFileEvent;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.openapi.wm.*;
+import com.intellij.util.xmlb.XmlSerializer;
+import com.jetbrains.python.edu.actions.StudyNextWindowAction;
+import com.jetbrains.python.edu.actions.StudyPrevWindowAction;
+import com.jetbrains.python.edu.actions.StudyShowHintAction;
+import com.jetbrains.python.edu.course.Course;
+import com.jetbrains.python.edu.course.Lesson;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.ui.StudyCondition;
+import com.jetbrains.python.edu.ui.StudyToolWindowFactory;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of class which contains all the information
+ * about study in context of current project
+ */
+
+@State(
+  name = "StudySettings",
+  storages = {
+    @Storage(
+      id = "others",
+      file = "$PROJECT_CONFIG_DIR$/study_project.xml",
+      scheme = StorageScheme.DIRECTORY_BASED
+    )}
+)
+public class StudyTaskManager implements ProjectComponent, PersistentStateComponent<Element>, DumbAware {
+  public static final String COURSE_ELEMENT = "courseElement";
+  private static Map<String, StudyTaskManager> myTaskManagers = new HashMap<String, StudyTaskManager>();
+  private static Map<String, String> myDeletedShortcuts = new HashMap<String, String>();
+  private final Project myProject;
+  private Course myCourse;
+  private FileCreatedListener myListener;
+
+
+  public void setCourse(Course course) {
+    myCourse = course;
+  }
+
+  private StudyTaskManager(@NotNull final Project project) {
+    myTaskManagers.put(project.getBasePath(), this);
+    myProject = project;
+  }
+
+
+  @Nullable
+  public Course getCourse() {
+    return myCourse;
+  }
+
+  @Nullable
+  @Override
+  public Element getState() {
+    Element el = new Element("taskManager");
+    if (myCourse != null) {
+      Element courseElement = new Element(COURSE_ELEMENT);
+      XmlSerializer.serializeInto(myCourse, courseElement);
+      el.addContent(courseElement);
+    }
+    return el;
+  }
+
+  @Override
+  public void loadState(Element el) {
+    myCourse = XmlSerializer.deserialize(el.getChild(COURSE_ELEMENT), Course.class);
+    if (myCourse != null) {
+      myCourse.init(true);
+    }
+  }
+
+  @Override
+  public void projectOpened() {
+    ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() {
+      @Override
+      public void run() {
+        ApplicationManager.getApplication().runWriteAction(new DumbAwareRunnable() {
+          @Override
+          public void run() {
+            if (myCourse != null) {
+              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);
+                    }
+                  }
+                }
+              });
+              UISettings.getInstance().HIDE_TOOL_STRIPES = false;
+              UISettings.getInstance().fireUISettingsChanged();
+              ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
+              String toolWindowId = StudyToolWindowFactory.STUDY_TOOL_WINDOW;
+              //TODO:decide smth with tool window position
+              try {
+                Method method = toolWindowManager.getClass().getDeclaredMethod("registerToolWindow", String.class,
+                                                                               JComponent.class,
+                                                                               ToolWindowAnchor.class,
+                                                                               boolean.class, boolean.class, boolean.class);
+                method.setAccessible(true);
+                method.invoke(toolWindowManager, toolWindowId, null, ToolWindowAnchor.LEFT, true, true, true);
+              }
+              catch (Exception e) {
+                final ToolWindow toolWindow = toolWindowManager.getToolWindow(toolWindowId);
+                if (toolWindow == null)
+                  toolWindowManager.registerToolWindow(toolWindowId, true, ToolWindowAnchor.RIGHT, myProject, true);
+              }
+
+              final ToolWindow studyToolWindow = toolWindowManager.getToolWindow(toolWindowId);
+              if (studyToolWindow != null) {
+                StudyUtils.updateStudyToolWindow(myProject);
+                studyToolWindow.show(null);
+              }
+              addShortcut(StudyNextWindowAction.SHORTCUT, StudyNextWindowAction.ACTION_ID);
+              addShortcut(StudyPrevWindowAction.SHORTCUT, StudyPrevWindowAction.ACTION_ID);
+              addShortcut(StudyShowHintAction.SHORTCUT, StudyShowHintAction.ACTION_ID);
+              addShortcut(StudyNextWindowAction.SHORTCUT2, StudyNextWindowAction.ACTION_ID);
+            }
+          }
+        });
+      }
+    });
+  }
+
+
+  private static void addShortcut(@NotNull final String shortcutString, @NotNull final String actionIdString) {
+    Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
+    Shortcut studyActionShortcut = new KeyboardShortcut(KeyStroke.getKeyStroke(shortcutString), null);
+    String[] actionsIds = keymap.getActionIds(studyActionShortcut);
+    for (String actionId : actionsIds) {
+      myDeletedShortcuts.put(actionId, shortcutString);
+      keymap.removeShortcut(actionId, studyActionShortcut);
+    }
+    keymap.addShortcut(actionIdString, studyActionShortcut);
+  }
+
+  @Override
+  public void projectClosed() {
+    StudyCondition.VALUE = false;
+    if (myCourse != null) {
+      ToolWindowManager.getInstance(myProject).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW).getContentManager()
+        .removeAllContents(false);
+      if (!myDeletedShortcuts.isEmpty()) {
+        for (Map.Entry<String, String> shortcut : myDeletedShortcuts.entrySet()) {
+          Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
+          Shortcut actionShortcut = new KeyboardShortcut(KeyStroke.getKeyStroke(shortcut.getValue()), null);
+          keymap.addShortcut(shortcut.getKey(), actionShortcut);
+        }
+      }
+    }
+  }
+
+  @Override
+  public void initComponent() {
+    EditorFactory.getInstance().addEditorFactoryListener(new StudyEditorFactoryListener(), myProject);
+    ActionManager.getInstance().addAnActionListener(new AnActionListener() {
+      @Override
+      public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
+        AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
+        for (AnAction newAction : newGroupActions) {
+          if (newAction == action) {
+            myListener =  new FileCreatedListener();
+            VirtualFileManager.getInstance().addVirtualFileListener(myListener);
+            break;
+          }
+        }
+      }
+
+      @Override
+      public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
+        AnAction[] newGroupActions = ((ActionGroup)ActionManager.getInstance().getAction("NewGroup")).getChildren(null);
+        for (AnAction newAction : newGroupActions) {
+          if (newAction == action) {
+            VirtualFileManager.getInstance().removeVirtualFileListener(myListener);
+          }
+        }
+      }
+
+      @Override
+      public void beforeEditorTyping(char c, DataContext dataContext) {
+
+      }
+    });
+  }
+
+  @Override
+  public void disposeComponent() {
+  }
+
+  @NotNull
+  @Override
+  public String getComponentName() {
+    return "StudyTaskManager";
+  }
+
+  public static StudyTaskManager getInstance(@NotNull final Project project) {
+    StudyTaskManager item = myTaskManagers.get(project.getBasePath());
+    return item != null ? item : new StudyTaskManager(project);
+  }
+
+
+  @Nullable
+  public TaskFile getTaskFile(@NotNull final VirtualFile file) {
+    if (myCourse == null) {
+      return null;
+    }
+    VirtualFile taskDir = file.getParent();
+    if (taskDir != null) {
+      String taskDirName = taskDir.getName();
+      if (taskDirName.contains(Task.TASK_DIR)) {
+        VirtualFile lessonDir = taskDir.getParent();
+        if (lessonDir != null) {
+          String lessonDirName = lessonDir.getName();
+          int lessonIndex = StudyUtils.getIndex(lessonDirName, Lesson.LESSON_DIR);
+          List<Lesson> lessons = myCourse.getLessons();
+          if (!StudyUtils.indexIsValid(lessonIndex, lessons)) {
+            return null;
+          }
+          Lesson lesson = lessons.get(lessonIndex);
+          int taskIndex = StudyUtils.getIndex(taskDirName, Task.TASK_DIR);
+          List<Task> tasks = lesson.getTaskList();
+          if (!StudyUtils.indexIsValid(taskIndex, tasks)) {
+            return null;
+          }
+          Task task = tasks.get(taskIndex);
+          return task.getFile(file.getName());
+        }
+      }
+    }
+    return null;
+  }
+
+  class FileCreatedListener extends VirtualFileAdapter {
+    @Override
+    public void fileCreated(@NotNull VirtualFileEvent event) {
+      VirtualFile createdFile = event.getFile();
+      VirtualFile taskDir = createdFile.getParent();
+      String taskLogicalName = Task.TASK_DIR;
+      if (taskDir != null && taskDir.getName().contains(taskLogicalName)) {
+        int taskIndex = StudyUtils.getIndex(taskDir.getName(), taskLogicalName);
+        VirtualFile lessonDir = taskDir.getParent();
+        String lessonLogicalName = Lesson.LESSON_DIR;
+        if (lessonDir != null && lessonDir.getName().contains(lessonLogicalName)) {
+          int lessonIndex = StudyUtils.getIndex(lessonDir.getName(), lessonLogicalName);
+          if (myCourse != null) {
+            List<Lesson> lessons = myCourse.getLessons();
+            if (StudyUtils.indexIsValid(lessonIndex, lessons)) {
+              Lesson lesson = lessons.get(lessonIndex);
+              List<Task> tasks = lesson.getTaskList();
+              if (StudyUtils.indexIsValid(taskIndex, tasks)) {
+                Task task = tasks.get(taskIndex);
+                TaskFile taskFile = new TaskFile();
+                taskFile.init(task, false);
+                taskFile.setUserCreated(true);
+                task.getTaskFiles().put(createdFile.getName(), taskFile);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+}
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
new file mode 100644
index 0000000..d3ac1da
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyUtils.java
@@ -0,0 +1,151 @@
+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.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.intellij.util.ui.UIUtil;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.course.TaskWindow;
+import com.jetbrains.python.edu.editor.StudyEditor;
+import com.jetbrains.python.edu.ui.StudyToolWindowFactory;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.*;
+import java.util.Collection;
+
+public class StudyUtils {
+  private static final Logger LOG = Logger.getInstance(StudyUtils.class.getName());
+  public static void closeSilently(Closeable stream) {
+    if (stream != null) {
+      try {
+        stream.close();
+      }
+      catch (IOException e) {
+        // close silently
+      }
+    }
+  }
+
+  public static boolean isZip(String fileName) {
+    return fileName.contains(".zip");
+  }
+
+  public static <T> T getFirst(Iterable<T> container) {
+    return container.iterator().next();
+  }
+
+  public static boolean indexIsValid(int index, Collection collection) {
+    int size = collection.size();
+    return index >= 0 && index < size;
+  }
+
+  @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+  @Nullable
+  public static String getFileText(String parentDir, String fileName, boolean wrapHTML) {
+
+    File inputFile = parentDir !=null ? new File(parentDir, fileName) : new File(fileName);
+    if (!inputFile.exists()) return null;
+    StringBuilder taskText = new StringBuilder();
+    BufferedReader reader = null;
+    try {
+      reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile)));
+      String line;
+      while ((line = reader.readLine()) != null) {
+        taskText.append(line).append("\n");
+        if (wrapHTML) {
+          taskText.append("<br>");
+        }
+      }
+      return wrapHTML ? UIUtil.toHtml(taskText.toString()) : taskText.toString();
+    }
+    catch (IOException e) {
+      LOG.error("Failed to get file text from file " + fileName, e);
+    }
+    finally {
+      closeSilently(reader);
+    }
+    return null;
+  }
+
+  public static void updateAction(AnActionEvent e) {
+    Presentation presentation = e.getPresentation();
+    presentation.setEnabled(false);
+    Project project = e.getProject();
+    if (project != null) {
+      FileEditor[] editors = FileEditorManager.getInstance(project).getAllEditors();
+      for (FileEditor editor : editors) {
+        if (editor instanceof StudyEditor) {
+          presentation.setEnabled(true);
+        }
+      }
+    }
+  }
+
+  public static void updateStudyToolWindow(Project project) {
+    ToolWindowManager.getInstance(project).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW).getContentManager().removeAllContents(false);
+    StudyToolWindowFactory factory =  new StudyToolWindowFactory();
+    factory.createToolWindowContent(project, ToolWindowManager.getInstance(project).getToolWindow(StudyToolWindowFactory.STUDY_TOOL_WINDOW));
+  }
+
+  public  static void synchronize() {
+    FileDocumentManager.getInstance().saveAllDocuments();
+    SaveAndSyncHandlerImpl.refreshOpenFiles();
+    VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
+  }
+
+  /**
+   * Gets number index in directory names like "task1", "lesson2"
+   *
+   * @param fullName    full name of directory
+   * @param logicalName part of name without index
+   * @return index of object
+   */
+  public static int getIndex(@NotNull final String fullName, @NotNull final String logicalName) {
+    if (!fullName.contains(logicalName)) {
+      throw new IllegalArgumentException();
+    }
+    return Integer.parseInt(fullName.substring(logicalName.length())) - 1;
+  }
+
+  @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+  public static VirtualFile flushWindows(Document document, TaskFile taskFile, VirtualFile file) {
+    VirtualFile taskDir = file.getParent();
+    VirtualFile fileWindows = 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()) {
+          if (!taskWindow.isValid(document)) {
+            continue;
+          }
+          int start = taskWindow.getRealStartOffset(document);
+          String windowDescription = document.getText(new TextRange(start, start + taskWindow.getLength()));
+          printWriter.println("#study_plugin_window = " + windowDescription);
+        }
+      }
+      catch (IOException e) {
+       LOG.error(e);
+      }
+      finally {
+        closeSilently(printWriter);
+        synchronize();
+      }
+    }
+    return fileWindows;
+  }
+}
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
new file mode 100644
index 0000000..f8e10c9
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyCheckAction.java
@@ -0,0 +1,340 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.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.popup.Balloon;
+import com.intellij.openapi.ui.popup.BalloonBuilder;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.JBColor;
+import com.jetbrains.python.edu.StudyDocumentListener;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.*;
+import com.jetbrains.python.edu.editor.StudyEditor;
+import 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;
+
+public class StudyCheckAction extends DumbAwareAction {
+
+  private static final Logger LOG = Logger.getInstance(StudyCheckAction.class.getName());
+  public static final String PYTHONPATH = "PYTHONPATH";
+
+  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;
+    }
+
+    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;
+    }
+
+
+    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;
+    }
+  }
+
+  public void check(@NotNull final Project project) {
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      @Override
+      public void run() {
+        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();
+              for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) {
+                String name = entry.getKey();
+                TaskFile taskFile = entry.getValue();
+                VirtualFile virtualFile = taskDir.findChild(name);
+                if (virtualFile == null) {
+                  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);
+              }
+            }
+          }
+        }
+
+         }
+      });
+      }
+    });
+  }
+
+  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);
+          }
+        });
+        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());
+        }
+      }
+    }
+    catch (IOException e) {
+      LOG.error(e);
+    }
+    catch (ExecutionException e) {
+      LOG.error(e);
+    }
+  }
+
+
+  private VirtualFile getCopyWithAnswers(final VirtualFile taskDir,
+                                         final VirtualFile file,
+                                         final TaskFile source,
+                                         TaskFile target) {
+    VirtualFile copy = null;
+    try {
+
+      copy = file.copy(this, taskDir, file.getNameWithoutExtension() +"_answers.py");
+      final FileDocumentManager documentManager = FileDocumentManager.getInstance();
+      final Document document = documentManager.getDocument(copy);
+      if (document != null) {
+        TaskFile.copy(source, target);
+        StudyDocumentListener listener = new StudyDocumentListener(target);
+        document.addDocumentListener(listener);
+        for (TaskWindow taskWindow : target.getTaskWindows()) {
+          if (!taskWindow.isValid(document)) {
+            continue;
+          }
+          final int start = taskWindow.getRealStartOffset(document);
+          final int end = start + taskWindow.getLength();
+          final String text = taskWindow.getPossibleAnswer();
+          document.replaceString(start, end, text);
+        }
+        ApplicationManager.getApplication().runWriteAction(new Runnable() {
+          @Override
+          public void run() {
+            documentManager.saveDocument(document);
+          }
+        });
+      }
+    }
+    catch (IOException e) {
+      LOG.error(e);
+    }
+
+
+    return copy;
+  }
+
+  private 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();
+    StudyEditor studyEditor = StudyEditor.getSelectedStudyEditor(project);
+    assert studyEditor != null;
+    JButton checkButton = studyEditor.getCheckButton();
+    balloon.showInCenterOf(checkButton);
+  }
+
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    if (project != null) {
+      check(project);
+    }
+  }
+}
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
new file mode 100644
index 0000000..5b9a6fe
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyEditInputAction.java
@@ -0,0 +1,213 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.ui.UISettings;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.popup.JBPopup;
+import com.intellij.openapi.ui.popup.JBPopupAdapter;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.popup.LightweightWindowEvent;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.IdeFocusManager;
+import com.intellij.ui.tabs.TabInfo;
+import com.intellij.ui.tabs.TabsListener;
+import com.intellij.ui.tabs.impl.JBEditorTabs;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.course.UserTest;
+import com.jetbrains.python.edu.editor.StudyEditor;
+import com.jetbrains.python.edu.ui.StudyTestContentPanel;
+import icons.StudyIcons;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class StudyEditInputAction extends DumbAwareAction {
+
+  public static final String TEST_TAB_NAME = "test";
+  public static final String USER_TEST_INPUT = "input";
+  public static final String USER_TEST_OUTPUT = "output";
+  private static final Logger LOG = Logger.getInstance(StudyEditInputAction.class.getName());
+  private JBEditorTabs tabbedPane;
+  private Map<TabInfo, UserTest> myEditableTabs = new HashMap<TabInfo, UserTest>();
+
+  public void showInput(final Project project) {
+    final Editor selectedEditor = StudyEditor.getSelectedEditor(project);
+    if (selectedEditor != null) {
+      FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+      final VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
+      StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project);
+      assert openedFile != null;
+      TaskFile taskFile = studyTaskManager.getTaskFile(openedFile);
+      assert taskFile != null;
+      final Task currentTask = taskFile.getTask();
+      tabbedPane = new JBEditorTabs(project, ActionManager.getInstance(), IdeFocusManager.findInstance(), project);
+      tabbedPane.addListener(new TabsListener.Adapter() {
+        @Override
+        public void selectionChanged(TabInfo oldSelection, TabInfo newSelection) {
+          if (newSelection.getIcon() != null) {
+            int tabCount = tabbedPane.getTabCount();
+            VirtualFile taskDir = openedFile.getParent();
+            VirtualFile testsDir = taskDir.findChild(Task.USER_TESTS);
+            assert testsDir != null;
+            UserTest userTest = createUserTest(testsDir, currentTask);
+            userTest.setEditable(true);
+            StudyTestContentPanel testContentPanel = new StudyTestContentPanel(userTest);
+            TabInfo testTab = addTestTab(tabbedPane.getTabCount(), testContentPanel, currentTask, true);
+            myEditableTabs.put(testTab, userTest);
+            tabbedPane.addTabSilently(testTab, tabCount - 1);
+            tabbedPane.select(testTab, true);
+          }
+        }
+      });
+      List<UserTest> userTests = currentTask.getUserTests();
+      int i = 1;
+      for (UserTest userTest : userTests) {
+        String inputFileText = StudyUtils.getFileText(null, userTest.getInput(), false);
+        String outputFileText = StudyUtils.getFileText(null, userTest.getOutput(), false);
+        StudyTestContentPanel myContentPanel = new StudyTestContentPanel(userTest);
+        myContentPanel.addInputContent(inputFileText);
+        myContentPanel.addOutputContent(outputFileText);
+        TabInfo testTab = addTestTab(i, myContentPanel, currentTask, userTest.isEditable());
+        tabbedPane.addTabSilently(testTab, i - 1);
+        if (userTest.isEditable()) {
+          myEditableTabs.put(testTab, userTest);
+        }
+        i++;
+      }
+      TabInfo plusTab = new TabInfo(new JPanel());
+      plusTab.setIcon(StudyIcons.Add);
+      tabbedPane.addTabSilently(plusTab, tabbedPane.getTabCount());
+      final JBPopup hint =
+        JBPopupFactory.getInstance().createComponentPopupBuilder(tabbedPane.getComponent(), tabbedPane.getComponent())
+          .setResizable(true)
+          .setMovable(true)
+          .setRequestFocus(true)
+          .createPopup();
+      StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
+      assert selectedStudyEditor != null;
+      hint.showInCenterOf(selectedStudyEditor.getComponent());
+      hint.addListener(new HintClosedListener(currentTask));
+    }
+  }
+
+
+  private static void flushBuffer(@NotNull final StringBuilder buffer, @NotNull final File file) {
+    PrintWriter printWriter = null;
+    try {
+      printWriter = new PrintWriter(new FileOutputStream(file));
+      printWriter.print(buffer.toString());
+    }
+    catch (FileNotFoundException e) {
+      LOG.error(e);
+    }
+    finally {
+      StudyUtils.closeSilently(printWriter);
+    }
+    StudyUtils.synchronize();
+  }
+
+  private static UserTest createUserTest(@NotNull final VirtualFile testsDir, @NotNull final Task currentTask) {
+    UserTest userTest = new UserTest();
+    List<UserTest> userTests = currentTask.getUserTests();
+    int testNum = userTests.size() + 1;
+    String inputName = USER_TEST_INPUT + testNum;
+    File inputFile = new File(testsDir.getPath(), inputName);
+    String outputName = USER_TEST_OUTPUT + testNum;
+    File outputFile = new File(testsDir.getPath(), outputName);
+    userTest.setInput(inputFile.getPath());
+    userTest.setOutput(outputFile.getPath());
+    userTests.add(userTest);
+    return userTest;
+  }
+
+  private TabInfo addTestTab(int nameIndex, final StudyTestContentPanel contentPanel, @NotNull final Task currentTask, boolean toBeClosable) {
+    TabInfo testTab = toBeClosable ? createClosableTab(contentPanel, currentTask) : new TabInfo(contentPanel);
+    return testTab.setText(TEST_TAB_NAME + String.valueOf(nameIndex));
+  }
+
+  private TabInfo createClosableTab(StudyTestContentPanel contentPanel, Task currentTask) {
+    TabInfo closableTab = new TabInfo(contentPanel);
+    final DefaultActionGroup tabActions = new DefaultActionGroup();
+    tabActions.add(new CloseTab(closableTab, currentTask));
+    closableTab.setTabLabelActions(tabActions, ActionPlaces.EDITOR_TAB);
+    return closableTab;
+  }
+
+  public void actionPerformed(AnActionEvent e) {
+    showInput(e.getProject());
+  }
+
+  private class HintClosedListener extends  JBPopupAdapter {
+    private final Task myTask;
+    private HintClosedListener(@NotNull final Task task) {
+      myTask = task;
+    }
+
+    @Override
+    public void onClosed(LightweightWindowEvent event) {
+      for (final UserTest userTest : myTask.getUserTests()) {
+        ApplicationManager.getApplication().runWriteAction(new Runnable() {
+          @Override
+          public void run() {
+            if (userTest.isEditable()) {
+              File inputFile = new File(userTest.getInput());
+              File outputFile = new File(userTest.getOutput());
+              flushBuffer(userTest.getInputBuffer(), inputFile);
+              flushBuffer(userTest.getOutputBuffer(), outputFile);
+            }
+          }
+        });
+      }
+    }
+  }
+
+  private class CloseTab extends AnAction implements DumbAware {
+
+    private final TabInfo myTabInfo;
+    private final Task myTask;
+
+    public CloseTab(final TabInfo info, @NotNull final Task task) {
+      myTabInfo = info;
+      myTask = task;
+    }
+
+    @Override
+    public void update(final AnActionEvent e) {
+      e.getPresentation().setIcon(tabbedPane.isEditorTabs() ? AllIcons.Actions.CloseNew : AllIcons.Actions.Close);
+      e.getPresentation().setHoveredIcon(tabbedPane.isEditorTabs() ? AllIcons.Actions.CloseNewHovered : AllIcons.Actions.CloseHovered);
+      e.getPresentation().setVisible(UISettings.getInstance().SHOW_CLOSE_BUTTON);
+      e.getPresentation().setText("Delete test");
+    }
+
+    @Override
+    public void actionPerformed(final AnActionEvent e) {
+      tabbedPane.removeTab(myTabInfo);
+      UserTest userTest = myEditableTabs.get(myTabInfo);
+      File testInputFile = new File(userTest.getInput());
+      File testOutputFile = new File(userTest.getOutput());
+      if (testInputFile.delete() && testOutputFile.delete()) {
+        StudyUtils.synchronize();
+      } else {
+        LOG.error("failed to delete user tests");
+      }
+      myTask.getUserTests().remove(userTest);
+    }
+  }
+}
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
new file mode 100644
index 0000000..81818a9
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextStudyTaskAction.java
@@ -0,0 +1,24 @@
+package com.jetbrains.python.edu.actions;
+
+import com.jetbrains.python.edu.editor.StudyEditor;
+import com.jetbrains.python.edu.course.Task;
+
+import javax.swing.*;
+
+public class StudyNextStudyTaskAction extends StudyTaskNavigationAction {
+
+  @Override
+  protected JButton getButton(StudyEditor selectedStudyEditor) {
+    return selectedStudyEditor.getNextTaskButton();
+  }
+
+  @Override
+  protected String getNavigationFinishedMessage() {
+    return "It's the last task";
+  }
+
+  @Override
+  protected Task getTargetTask(Task sourceTask) {
+    return sourceTask.next();
+  }
+}
\ 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
new file mode 100644
index 0000000..595aeef
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNextWindowAction.java
@@ -0,0 +1,32 @@
+package com.jetbrains.python.edu.actions;
+
+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;
+
+/**
+ * move caret to next task window
+ */
+public class StudyNextWindowAction extends StudyWindowNavigationAction {
+  public static final String ACTION_ID = "NextWindow";
+  public static final String SHORTCUT = "ctrl pressed PERIOD";
+  public static final String SHORTCUT2 = "ctrl pressed ENTER";
+
+  public StudyNextWindowAction() {
+    super("NextWindowAction", "Select next window", StudyIcons.Next);
+  }
+
+  @Override
+  protected TaskWindow getNextTaskWindow(@NotNull final TaskWindow window) {
+    int index = window.getIndex();
+    List<TaskWindow> windows = window.getTaskFile().getTaskWindows();
+    if (StudyUtils.indexIsValid(index, windows)) {
+      int newIndex = index + 1;
+        return newIndex == windows.size() ? windows.get(0) : windows.get(newIndex);
+    }
+    return null;
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPrevWindowAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPrevWindowAction.java
new file mode 100644
index 0000000..3474561
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPrevWindowAction.java
@@ -0,0 +1,34 @@
+package com.jetbrains.python.edu.actions;
+
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.TaskWindow;
+import icons.StudyIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * author: liana
+ * data: 6/30/14.
+ */
+public class StudyPrevWindowAction extends StudyWindowNavigationAction {
+  public static final String ACTION_ID = "PrevWindowAction";
+  public static final String SHORTCUT = "ctrl pressed COMMA";
+
+  public StudyPrevWindowAction() {
+    super("PrevWindowAction", "Select previous window", StudyIcons.Prev);
+  }
+
+
+  @Nullable
+  @Override
+  protected TaskWindow getNextTaskWindow(@NotNull final TaskWindow window) {
+    int prevIndex = window.getIndex() - 1;
+    List<TaskWindow> windows = window.getTaskFile().getTaskWindows();
+    if (StudyUtils.indexIsValid(prevIndex, windows)) {
+      return windows.get(prevIndex);
+    }
+    return null;
+  }
+}
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
new file mode 100644
index 0000000..bc26c28
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyPreviousStudyTaskAction.java
@@ -0,0 +1,25 @@
+package com.jetbrains.python.edu.actions;
+
+
+import com.jetbrains.python.edu.editor.StudyEditor;
+import com.jetbrains.python.edu.course.Task;
+
+import javax.swing.*;
+
+public class StudyPreviousStudyTaskAction extends StudyTaskNavigationAction {
+
+  @Override
+  protected JButton getButton(StudyEditor selectedStudyEditor) {
+    return selectedStudyEditor.getPrevTaskButton();
+  }
+
+  @Override
+  protected String getNavigationFinishedMessage() {
+    return "It's already the first task";
+  }
+
+  @Override
+  protected Task getTargetTask(Task sourceTask) {
+    return sourceTask.prev();
+  }
+}
\ 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/StudyRefreshTaskAction.java
new file mode 100644
index 0000000..f8abb0b
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskAction.java
@@ -0,0 +1,122 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.ui.popup.Balloon;
+import com.intellij.openapi.ui.popup.BalloonBuilder;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.IdeFocusManager;
+import com.jetbrains.python.edu.StudyDocumentListener;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.*;
+import com.jetbrains.python.edu.editor.StudyEditor;
+
+import java.io.*;
+
+public class StudyRefreshTaskAction extends DumbAwareAction {
+  private static final Logger LOG = Logger.getInstance(StudyRefreshTaskAction.class.getName());
+
+  public void refresh(final Project project) {
+        ApplicationManager.getApplication().invokeLater(new Runnable() {
+          @Override
+          public void run() {
+            ApplicationManager.getApplication().runWriteAction(new Runnable() {
+              @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+              @Override
+              public void run() {
+                final Editor editor = StudyEditor.getSelectedEditor(project);
+                assert editor != null;
+                final Document document = editor.getDocument();
+                StudyDocumentListener listener = StudyEditor.getListener(document);
+                if (listener != null) {
+                  document.removeDocumentListener(listener);
+                }
+                final int lineCount = document.getLineCount();
+                if (lineCount != 0) {
+                  CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
+                    @Override
+                    public void run() {
+                      document.deleteString(0, document.getLineEndOffset(lineCount - 1));
+                    }
+                  });
+                }
+                StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+                Course course = taskManager.getCourse();
+                assert course != null;
+                File resourceFile = new File(course.getResourcePath());
+                File resourceRoot = resourceFile.getParentFile();
+                FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+                VirtualFile openedFile = fileDocumentManager.getFile(document);
+                assert openedFile != null;
+                final TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
+                assert selectedTaskFile != null;
+                Task currentTask = selectedTaskFile.getTask();
+                String lessonDir = Lesson.LESSON_DIR + String.valueOf(currentTask.getLesson().getIndex() + 1);
+                String taskDir = Task.TASK_DIR + String.valueOf(currentTask.getIndex() + 1);
+                File pattern = new File(new File(new File(resourceRoot, lessonDir), taskDir), openedFile.getName());
+                BufferedReader reader = null;
+                try {
+                  reader = new BufferedReader(new InputStreamReader(new FileInputStream(pattern)));
+                  String line;
+                  StringBuilder patternText = new StringBuilder();
+                  while ((line = reader.readLine()) != null) {
+                    patternText.append(line);
+                    patternText.append("\n");
+                  }
+                  int patternLength = patternText.length();
+                  if (patternText.charAt(patternLength - 1) == '\n') {
+                    patternText.delete(patternLength - 1, patternLength);
+                  }
+                  document.setText(patternText);
+                  StudyStatus oldStatus = currentTask.getStatus();
+                  LessonInfo lessonInfo = currentTask.getLesson().getLessonInfo();
+                  lessonInfo.update(oldStatus, -1);
+                  lessonInfo.update(StudyStatus.Unchecked, +1);
+                  StudyUtils.updateStudyToolWindow(project);
+                  for (TaskWindow taskWindow : selectedTaskFile.getTaskWindows()) {
+                    taskWindow.reset();
+                  }
+                  ProjectView.getInstance(project).refresh();
+                  if (listener != null) {
+                    document.addDocumentListener(listener);
+                  }
+                  selectedTaskFile.drawAllWindows(editor);
+                  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();
+                  StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
+                  assert selectedStudyEditor != null;
+                  balloon.showInCenterOf(selectedStudyEditor.getRefreshButton());
+                }
+                catch (FileNotFoundException e1) {
+                  LOG.error(e1);
+                }
+                catch (IOException e1) {
+                  LOG.error(e1);
+                }
+                finally {
+                  StudyUtils.closeSilently(reader);
+                }
+              }
+            });
+          }
+        });
+  }
+
+  public void actionPerformed(AnActionEvent e) {
+    refresh(e.getProject());
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java
new file mode 100644
index 0000000..71e95de
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRunAction.java
@@ -0,0 +1,89 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.RunContentExecutor;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.execution.process.OSProcessHandler;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.python.sdk.PythonSdkType;
+import com.jetbrains.python.edu.StudyResourceManger;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.editor.StudyEditor;
+
+import java.io.File;
+
+public class StudyRunAction extends DumbAwareAction {
+  private static final Logger LOG = Logger.getInstance(StudyRunAction.class.getName());
+  public static final String ACTION_ID = "StudyRunAction";
+
+  public void run(Project project) {
+    Editor selectedEditor = StudyEditor.getSelectedEditor(project);
+    FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+    assert selectedEditor != null;
+    VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
+    StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+    if (openedFile != null && openedFile.getCanonicalPath() != null) {
+      String filePath = openedFile.getCanonicalPath();
+      GeneralCommandLine cmd = new GeneralCommandLine();
+      cmd.setWorkDirectory(openedFile.getParent().getCanonicalPath());
+      Sdk sdk = PythonSdkType.findPythonSdk(ModuleManager.getInstance(project).getModules()[0]);
+      if (sdk != null) {
+        String pythonPath = sdk.getHomePath();
+        if (pythonPath != null) {
+          cmd.setExePath(pythonPath);
+          TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
+          assert selectedTaskFile != null;
+          Task currentTask = selectedTaskFile.getTask();
+          if (!currentTask.getUserTests().isEmpty()) {
+            cmd.addParameter(new File(project.getBaseDir().getPath(), StudyResourceManger.USER_TESTER).getPath());
+            cmd.addParameter(pythonPath);
+            cmd.addParameter(filePath);
+            Process p;
+            try {
+              p = cmd.createProcess();
+            }
+            catch (ExecutionException e) {
+              LOG.error(e);
+              return;
+            }
+            ProcessHandler handler = new OSProcessHandler(p);
+
+            RunContentExecutor executor = new RunContentExecutor(project, handler);
+            Disposer.register(project, executor);
+            executor.run();
+            return;
+          }
+          try {
+            cmd.addParameter(filePath);
+            Process p = cmd.createProcess();
+            ProcessHandler handler = new OSProcessHandler(p);
+
+            RunContentExecutor executor = new RunContentExecutor(project, handler);
+            Disposer.register(project, executor);
+            executor.run();
+          }
+
+          catch (ExecutionException e) {
+            LOG.error(e);
+          }
+        }
+      }
+    }
+  }
+
+  public void actionPerformed(AnActionEvent e) {
+    run(e.getProject());
+  }
+}
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
new file mode 100644
index 0000000..1efa908
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyShowHintAction.java
@@ -0,0 +1,95 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.codeInsight.documentation.DocumentationComponent;
+import com.intellij.codeInsight.documentation.DocumentationManager;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.popup.JBPopup;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.Course;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.course.TaskWindow;
+import com.jetbrains.python.edu.editor.StudyEditor;
+import icons.StudyIcons;
+
+import java.io.File;
+
+public class StudyShowHintAction extends DumbAwareAction {
+  public static final String ACTION_ID = "ShowHintAction";
+  public static final String SHORTCUT = "ctrl pressed 7";
+
+  public StudyShowHintAction() {
+    super("Show hint", "Show hint", StudyIcons.ShowHint);
+  }
+
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    if (project != null) {
+      DocumentationManager documentationManager = DocumentationManager.getInstance(project);
+      DocumentationComponent component = new DocumentationComponent(documentationManager);
+      Editor selectedEditor = StudyEditor.getSelectedEditor(project);
+      FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+      assert selectedEditor != null;
+      VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
+      if (openedFile != null) {
+        StudyTaskManager taskManager = StudyTaskManager.getInstance(e.getProject());
+        TaskFile taskFile = taskManager.getTaskFile(openedFile);
+        if (taskFile != null) {
+          PsiFile file = PsiManager.getInstance(project).findFile(openedFile);
+          if (file != null) {
+            LogicalPosition pos = selectedEditor.getCaretModel().getLogicalPosition();
+            TaskWindow taskWindow = taskFile.getTaskWindow(selectedEditor.getDocument(), pos);
+            if (taskWindow != null) {
+              String hint = taskWindow.getHint();
+              if (hint == null) {
+                return;
+              }
+              Course course = taskManager.getCourse();
+              if (course != null) {
+                File resourceFile = new File(course.getResourcePath());
+                File resourceRoot = resourceFile.getParentFile();
+                if (resourceRoot != null && resourceRoot.exists()) {
+                  File hintsDir = new File(resourceRoot, Course.HINTS_DIR);
+                  if (hintsDir.exists()) {
+                    String hintText = StudyUtils.getFileText(hintsDir.getAbsolutePath(), hint, true);
+                    if (hintText != null) {
+                      int offset = selectedEditor.getDocument().getLineStartOffset(pos.line) + pos.column;
+                      PsiElement element = file.findElementAt(offset);
+                      if (element != null) {
+                        component.setData(element, hintText, true, null);
+                        final JBPopup popup =
+                          JBPopupFactory.getInstance().createComponentPopupBuilder(component, component)
+                            .setDimensionServiceKey(project, DocumentationManager.JAVADOC_LOCATION_AND_SIZE, false)
+                            .setResizable(true)
+                            .setMovable(true)
+                            .setRequestFocus(true)
+                            .createPopup();
+                        component.setHint(popup);
+                        popup.showInBestPositionFor(selectedEditor);
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  @Override
+  public void update(AnActionEvent e) {
+    StudyUtils.updateAction(e);
+  }
+}
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
new file mode 100644
index 0000000..b781e7d
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
@@ -0,0 +1,97 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.ui.popup.Balloon;
+import com.intellij.openapi.ui.popup.BalloonBuilder;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.course.Lesson;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.editor.StudyEditor;
+
+import javax.swing.*;
+import java.util.Map;
+
+/**
+ * author: liana
+ * data: 7/21/14.
+ */
+abstract public class 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);
+    if (nextTask == null) {
+      BalloonBuilder balloonBuilder =
+        JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(getNavigationFinishedMessage(), MessageType.INFO, null);
+      Balloon balloon = balloonBuilder.createBalloon();
+      StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
+      balloon.showInCenterOf(getButton(selectedStudyEditor));
+      return;
+    }
+    for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) {
+      FileEditorManager.getInstance(project).closeFile(file);
+    }
+    int nextTaskIndex = nextTask.getIndex();
+    int lessonIndex = nextTask.getLesson().getIndex();
+    Map<String, TaskFile> nextTaskFiles = nextTask.getTaskFiles();
+    if (nextTaskFiles.isEmpty()) {
+      return;
+    }
+    VirtualFile projectDir = project.getBaseDir();
+    String lessonDirName = Lesson.LESSON_DIR + String.valueOf(lessonIndex + 1);
+    if (projectDir == null) {
+      return;
+    }
+    VirtualFile lessonDir = projectDir.findChild(lessonDirName);
+    if (lessonDir == null) {
+      return;
+    }
+    String taskDirName = Task.TASK_DIR + String.valueOf(nextTaskIndex + 1);
+    VirtualFile taskDir = lessonDir.findChild(taskDirName);
+    if (taskDir == null) {
+      return;
+    }
+    VirtualFile shouldBeActive = null;
+    for (Map.Entry<String, TaskFile> entry : nextTaskFiles.entrySet()) {
+      String name = entry.getKey();
+      TaskFile taskFile = entry.getValue();
+      VirtualFile vf = taskDir.findChild(name);
+      if (vf != null) {
+        FileEditorManager.getInstance(project).openFile(vf, true);
+        if (!taskFile.getTaskWindows().isEmpty()) {
+          shouldBeActive = vf;
+        }
+      }
+    }
+    if (shouldBeActive != null) {
+      FileEditorManager.getInstance(project).openFile(shouldBeActive, true);
+    }
+  }
+
+  protected abstract JButton getButton(StudyEditor selectedStudyEditor);
+
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    navigateTask(e.getProject());
+  }
+
+  protected abstract String getNavigationFinishedMessage();
+
+  protected abstract Task getTargetTask(Task sourceTask);
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyWindowNavigationAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyWindowNavigationAction.java
new file mode 100644
index 0000000..8c6b902
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyWindowNavigationAction.java
@@ -0,0 +1,65 @@
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.TaskFile;
+import com.jetbrains.python.edu.course.TaskWindow;
+import com.jetbrains.python.edu.editor.StudyEditor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+abstract public class StudyWindowNavigationAction extends DumbAwareAction {
+
+  public StudyWindowNavigationAction(String actionId, String description, Icon icon) {
+    super(actionId, description, icon);
+  }
+
+  public void navigateWindow(@NotNull final Project project) {
+      Editor selectedEditor = StudyEditor.getSelectedEditor(project);
+      if (selectedEditor != null) {
+        FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+        VirtualFile openedFile = fileDocumentManager.getFile(selectedEditor.getDocument());
+        if (openedFile != null) {
+          StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+          TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
+          if (selectedTaskFile != null) {
+            TaskWindow selectedTaskWindow = selectedTaskFile.getSelectedTaskWindow();
+            if (selectedTaskWindow == null) {
+              return;
+            }
+            TaskWindow nextTaskWindow = getNextTaskWindow(selectedTaskWindow);
+            if (nextTaskWindow == null) {
+              return;
+            }
+            nextTaskWindow.draw(selectedEditor, true, true);
+            selectedTaskFile.setSelectedTaskWindow(nextTaskWindow);
+            }
+          }
+        }
+      }
+
+  @Nullable
+  protected abstract TaskWindow getNextTaskWindow(@NotNull final TaskWindow window);
+
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    Project project = e.getProject();
+    if (project == null) {
+      return;
+    }
+    navigateWindow(project);
+  }
+
+  @Override
+  public void update(AnActionEvent e) {
+    StudyUtils.updateAction(e);
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Course.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Course.java
new file mode 100644
index 0000000..89613ac
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Course.java
@@ -0,0 +1,104 @@
+package com.jetbrains.python.edu.course;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Course {
+
+  private static final Logger LOG = Logger.getInstance(Course.class.getName());
+  public static final String PLAYGROUND_DIR = "Playground";
+  public List<Lesson> lessons = new ArrayList<Lesson>();
+  public String description;
+  public String name;
+  public String myResourcePath = "";
+  public String author;
+  public static final String COURSE_DIR = "course";
+  public static final String HINTS_DIR = "hints";
+
+
+  public List<Lesson> getLessons() {
+    return lessons;
+  }
+
+  /**
+   * Initializes state of course
+   */
+  public void init(boolean isRestarted) {
+    for (Lesson lesson : lessons) {
+      lesson.init(this, isRestarted);
+    }
+  }
+
+  public String getAuthor() {
+    return author;
+  }
+
+  /**
+   * Creates course directory in project user created
+   *
+   * @param baseDir      project directory
+   * @param resourceRoot directory where original course is stored
+   */
+  public void create(@NotNull final VirtualFile baseDir, @NotNull final File resourceRoot) {
+    ApplicationManager.getApplication().invokeLater(
+      new Runnable() {
+        @Override
+        public void run() {
+          ApplicationManager.getApplication().runWriteAction(new Runnable() {
+            @Override
+            public void run() {
+              try {
+                for (int i = 0; i < lessons.size(); i++) {
+                  Lesson lesson = lessons.get(i);
+                  lesson.setIndex(i);
+                  lesson.create(baseDir, resourceRoot);
+                }
+                baseDir.createChildDirectory(this, PLAYGROUND_DIR);
+                File[] files = resourceRoot.listFiles(new FilenameFilter() {
+                  @Override
+                  public boolean accept(File dir, String name) {
+                   return !name.contains(Lesson.LESSON_DIR) && !name.equals("course.json") && !name.equals("hints");
+                  }
+                });
+                for (File file: files) {
+                  FileUtil.copy(file, new File(baseDir.getPath(), file.getName()));
+                }
+              }
+              catch (IOException e) {
+                LOG.error(e);
+              }
+            }
+          });
+        }
+      });
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setResourcePath(@NotNull final String resourcePath) {
+    myResourcePath = resourcePath;
+  }
+
+  public String getResourcePath() {
+    return myResourcePath;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/CourseInfo.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/CourseInfo.java
new file mode 100644
index 0000000..9f820c1
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/CourseInfo.java
@@ -0,0 +1,52 @@
+package com.jetbrains.python.edu.course;
+
+/**
+ * Implementation of class which contains information to be shawn in course description in tool window
+ * and when project is being created
+ */
+public class CourseInfo {
+  private String myName;
+  private String myAuthor;
+  private String myDescription;
+  public static CourseInfo INVALID_COURSE = new CourseInfo("", "", "");
+
+  public CourseInfo(String name, String author, String description) {
+    myName = name;
+    myAuthor = author;
+    myDescription = description;
+  }
+
+  public String getName() {
+    return myName;
+  }
+
+  public String getAuthor() {
+    return myAuthor;
+  }
+
+  public String getDescription() {
+    return myDescription;
+  }
+
+  @Override
+  public String toString() {
+    return myName;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    CourseInfo that = (CourseInfo)o;
+    return that.getName().equals(myName) && that.getAuthor().equals(myAuthor)
+           && that.getDescription().equals(myDescription);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = myName != null ? myName.hashCode() : 0;
+    result = 31 * result + (myAuthor != null ? myAuthor.hashCode() : 0);
+    result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0);
+    return result;
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Lesson.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Lesson.java
new file mode 100644
index 0000000..3879d51
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Lesson.java
@@ -0,0 +1,109 @@
+package com.jetbrains.python.edu.course;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.xmlb.annotations.Transient;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Lesson implements Stateful{
+  public String name;
+  public List<Task> taskList = new ArrayList<Task>();
+  private Course myCourse = null;
+  public int myIndex = -1;
+  public static final String LESSON_DIR = "lesson";
+  public LessonInfo myLessonInfo = new LessonInfo();
+
+  public LessonInfo getLessonInfo() {
+    return myLessonInfo;
+  }
+
+  @Transient
+  public StudyStatus getStatus() {
+    for (Task task : taskList) {
+      StudyStatus taskStatus = task.getStatus();
+      if (taskStatus == StudyStatus.Unchecked || taskStatus == StudyStatus.Failed) {
+        return StudyStatus.Unchecked;
+      }
+    }
+    return StudyStatus.Solved;
+  }
+
+  @Override
+  public void setStatus(StudyStatus status, StudyStatus oldStatus) {
+    for (Task task : taskList) {
+      task.setStatus(status, oldStatus);
+    }
+  }
+
+  public List<Task> getTaskList() {
+    return taskList;
+  }
+
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  /**
+   * Creates lesson directory in its course folder in project user created
+   *
+   * @param courseDir    project directory of course
+   * @param resourceRoot directory where original lesson stored
+   * @throws java.io.IOException
+   */
+  public void create(@NotNull final VirtualFile courseDir, @NotNull final File resourceRoot) throws IOException {
+    String lessonDirName = LESSON_DIR + Integer.toString(myIndex + 1);
+    VirtualFile lessonDir = courseDir.createChildDirectory(this, lessonDirName);
+    for (int i = 0; i < taskList.size(); i++) {
+      Task task = taskList.get(i);
+      task.setIndex(i);
+      task.create(lessonDir, new File(resourceRoot, lessonDir.getName()));
+    }
+  }
+
+
+  /**
+   * Initializes state of lesson
+   *
+   * @param course course which lesson belongs to
+   */
+  public void init(final Course course, boolean isRestarted) {
+    myCourse = course;
+    myLessonInfo.setTaskNum(taskList.size());
+    myLessonInfo.setTaskUnchecked(taskList.size());
+    for (Task task : taskList) {
+      task.init(this, isRestarted);
+    }
+  }
+
+  public Lesson next() {
+    List<Lesson> lessons = myCourse.getLessons();
+    if (myIndex + 1 >= lessons.size()) {
+      return null;
+    }
+    return lessons.get(myIndex + 1);
+  }
+
+  public void setIndex(int index) {
+    myIndex = index;
+  }
+
+  public int getIndex() {
+    return myIndex;
+  }
+
+  public Lesson prev() {
+    if (myIndex - 1 < 0) {
+      return null;
+    }
+    return myCourse.getLessons().get(myIndex - 1);
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/LessonInfo.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/LessonInfo.java
new file mode 100644
index 0000000..85e2eb8
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/LessonInfo.java
@@ -0,0 +1,60 @@
+package com.jetbrains.python.edu.course;
+
+/**
+ * Implementation of class which contains information about student progress in current lesson
+ */
+public class LessonInfo {
+  private int myTaskNum;
+  private int myTaskFailed;
+  private int myTaskSolved;
+  private int myTaskUnchecked;
+
+  public int getTaskNum() {
+    return myTaskNum;
+  }
+
+  public void setTaskNum(int taskNum) {
+    myTaskNum = taskNum;
+  }
+
+  public int getTaskFailed() {
+    return myTaskFailed;
+  }
+
+  public void setTaskFailed(int taskFailed) {
+    myTaskFailed = taskFailed;
+  }
+
+  public int getTaskSolved() {
+    return myTaskSolved;
+  }
+
+  public void setTaskSolved(int taskSolved) {
+    myTaskSolved = taskSolved;
+  }
+
+  public int getTaskUnchecked() {
+    return myTaskUnchecked;
+  }
+
+  public void setTaskUnchecked(int taskUnchecked) {
+    myTaskUnchecked = taskUnchecked;
+  }
+
+  public void update(StudyStatus status, int delta) {
+    switch (status) {
+      case Solved: {
+        myTaskSolved += delta;
+        break;
+      }
+      case Failed: {
+        myTaskFailed += delta;
+        break;
+      }
+      case Unchecked: {
+        myTaskUnchecked += delta;
+        break;
+      }
+    }
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Stateful.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Stateful.java
new file mode 100644
index 0000000..3a16362
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Stateful.java
@@ -0,0 +1,6 @@
+package com.jetbrains.python.edu.course;
+
+public interface Stateful {
+  StudyStatus getStatus();
+  void setStatus(StudyStatus status, StudyStatus oldStatus);
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/StudyStatus.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/StudyStatus.java
new file mode 100644
index 0000000..d95b42b
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/StudyStatus.java
@@ -0,0 +1,8 @@
+package com.jetbrains.python.edu.course;
+
+/**
+ * @see {@link TaskWindow#myStatus}
+ */
+public enum StudyStatus {
+  Unchecked, Solved, Failed
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/Task.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Task.java
new file mode 100644
index 0000000..2323412
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/Task.java
@@ -0,0 +1,201 @@
+package com.jetbrains.python.edu.course;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.xmlb.annotations.Transient;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import com.jetbrains.python.edu.StudyUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of task which contains task files, tests, input file for tests
+ */
+public class Task implements Stateful{
+  public static final String TASK_DIR = "task";
+  private static final String ourTestFile = "tests.py";
+  public String name;
+  private static final String ourTextFile = "task.html";
+  public Map<String, TaskFile> taskFiles = new HashMap<String, TaskFile>();
+  private Lesson myLesson;
+  public int myIndex;
+  public List<UserTest> userTests = new ArrayList<UserTest>();
+  public static final String USER_TESTS = "userTests";
+
+  public Map<String, TaskFile> getTaskFiles() {
+    return taskFiles;
+  }
+
+  @Transient
+  public StudyStatus getStatus() {
+    for (TaskFile taskFile : taskFiles.values()) {
+      StudyStatus taskFileStatus = taskFile.getStatus();
+      if (taskFileStatus == StudyStatus.Unchecked) {
+        return StudyStatus.Unchecked;
+      }
+      if (taskFileStatus == StudyStatus.Failed) {
+        return StudyStatus.Failed;
+      }
+    }
+    return StudyStatus.Solved;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public void setStatus(@NotNull final StudyStatus status, @NotNull final StudyStatus oldStatus) {
+    LessonInfo lessonInfo = myLesson.getLessonInfo();
+    if (status != oldStatus) {
+      lessonInfo.update(oldStatus, -1);
+      lessonInfo.update(status, +1);
+    }
+    for (TaskFile taskFile : taskFiles.values()) {
+      taskFile.setStatus(status, oldStatus);
+    }
+  }
+
+  public List<UserTest> getUserTests() {
+    return userTests;
+  }
+
+  public String getTestFile() {
+    return ourTestFile;
+  }
+
+  public String getText() {
+    return ourTextFile;
+  }
+
+  /**
+   * Creates task directory in its lesson folder in project user created
+   *
+   * @param lessonDir    project directory of lesson which task belongs to
+   * @param resourceRoot directory where original task file stored
+   * @throws java.io.IOException
+   */
+  public void create(@NotNull final VirtualFile lessonDir, @NotNull final File resourceRoot) throws IOException {
+    VirtualFile taskDir = lessonDir.createChildDirectory(this, TASK_DIR + Integer.toString(myIndex + 1));
+    File newResourceRoot = new File(resourceRoot, taskDir.getName());
+    int i = 0;
+    for (Map.Entry<String, TaskFile> taskFile : taskFiles.entrySet()) {
+      TaskFile taskFileContent = taskFile.getValue();
+      taskFileContent.setIndex(i);
+      i++;
+      taskFileContent.create(taskDir, newResourceRoot, taskFile.getKey());
+    }
+    File[] filesInTask = newResourceRoot.listFiles();
+    if (filesInTask != null) {
+      for (File file : filesInTask) {
+        String fileName = file.getName();
+        if (!isTaskFile(fileName)) {
+          File resourceFile = new File(newResourceRoot, fileName);
+          File fileInProject = new File(taskDir.getCanonicalPath(), fileName);
+          FileUtil.copy(resourceFile, fileInProject);
+        }
+      }
+    }
+  }
+
+  private boolean isTaskFile(@NotNull final String fileName) {
+    return taskFiles.get(fileName) != null;
+  }
+
+  @Nullable
+  public TaskFile getFile(@NotNull final String fileName) {
+    return taskFiles.get(fileName);
+  }
+
+  /**
+   * Initializes state of task file
+   *
+   * @param lesson lesson which task belongs to
+   */
+  public void init(final Lesson lesson, boolean isRestarted) {
+    myLesson = lesson;
+    for (TaskFile taskFile : taskFiles.values()) {
+      taskFile.init(this, isRestarted);
+    }
+  }
+
+  public Task next() {
+    Lesson currentLesson = this.myLesson;
+    List<Task> taskList = myLesson.getTaskList();
+    if (myIndex + 1 < taskList.size()) {
+      return taskList.get(myIndex + 1);
+    }
+    Lesson nextLesson = currentLesson.next();
+    if (nextLesson == null) {
+      return null;
+    }
+    return StudyUtils.getFirst(nextLesson.getTaskList());
+  }
+
+  public Task prev() {
+    Lesson currentLesson = this.myLesson;
+    if (myIndex - 1 >= 0) {
+      return myLesson.getTaskList().get(myIndex - 1);
+    }
+    Lesson prevLesson = currentLesson.prev();
+    if (prevLesson == null) {
+      return null;
+    }
+    //getting last task in previous lesson
+    return prevLesson.getTaskList().get(prevLesson.getTaskList().size() - 1);
+  }
+
+  public void setIndex(int index) {
+    myIndex = index;
+  }
+
+  public int getIndex() {
+    return myIndex;
+  }
+
+  public Lesson getLesson() {
+    return myLesson;
+  }
+
+
+  @Nullable
+  public VirtualFile getTaskDir(Project project) {
+    String lessonDirName = Lesson.LESSON_DIR + String.valueOf(myLesson.getIndex() + 1);
+    String taskDirName = TASK_DIR + String.valueOf(myIndex + 1);
+    VirtualFile courseDir = project.getBaseDir();
+    if (courseDir != null) {
+      VirtualFile lessonDir = courseDir.findChild(lessonDirName);
+      if (lessonDir != null) {
+        return lessonDir.findChild(taskDirName);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Gets text of resource file such as test input file or task text in needed format
+   *
+   * @param fileName name of resource file which should exist in task directory
+   * @param wrapHTML if it's necessary to wrap text with html tags
+   * @return text of resource file wrapped with html tags if necessary
+   */
+  @Nullable
+  public String getResourceText(@NotNull final Project project, @NotNull final String fileName, boolean wrapHTML) {
+    VirtualFile taskDir = getTaskDir(project);
+    if (taskDir != null) {
+      return StudyUtils.getFileText(taskDir.getCanonicalPath(), fileName, wrapHTML);
+    }
+    return null;
+  }
+
+}
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
new file mode 100644
index 0000000..4f17fc0
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskFile.java
@@ -0,0 +1,228 @@
+package com.jetbrains.python.edu.course;
+
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.xmlb.annotations.Transient;
+import com.jetbrains.python.edu.StudyUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of task file which contains task windows for student to type in and
+ * which is visible to student in project view
+ */
+
+public class TaskFile implements Stateful{
+  public List<TaskWindow> taskWindows = new ArrayList<TaskWindow>();
+  private Task myTask;
+  @Transient
+  private TaskWindow mySelectedTaskWindow = null;
+  public int myIndex = -1;
+  private boolean myUserCreated = false;
+
+  /**
+   * @return if all the windows in task file are marked as resolved
+   */
+  @Transient
+  public StudyStatus getStatus() {
+    for (TaskWindow taskWindow : taskWindows) {
+      StudyStatus windowStatus = taskWindow.getStatus();
+      if (windowStatus == StudyStatus.Failed) {
+        return StudyStatus.Failed;
+      }
+      if (windowStatus == StudyStatus.Unchecked) {
+        return StudyStatus.Unchecked;
+      }
+    }
+    return StudyStatus.Solved;
+  }
+
+  public Task getTask() {
+    return myTask;
+  }
+
+  @Nullable
+  @Transient
+  public TaskWindow getSelectedTaskWindow() {
+    return mySelectedTaskWindow;
+  }
+
+  /**
+   * @param selectedTaskWindow window from this task file to be set as selected
+   */
+  public void setSelectedTaskWindow(@NotNull final TaskWindow selectedTaskWindow) {
+    if (selectedTaskWindow.getTaskFile() == this) {
+      mySelectedTaskWindow = selectedTaskWindow;
+    }
+    else {
+      throw new IllegalArgumentException("Window may be set as selected only in task file which it belongs to");
+    }
+  }
+
+  public List<TaskWindow> getTaskWindows() {
+    return taskWindows;
+  }
+
+  /**
+   * Creates task files in its task folder in project user created
+   *
+   * @param taskDir      project directory of task which task file belongs to
+   * @param resourceRoot directory where original task file stored
+   * @throws java.io.IOException
+   */
+  public void create(@NotNull final VirtualFile taskDir, @NotNull final File resourceRoot,
+                     @NotNull final String name) throws IOException {
+    String systemIndependentName = FileUtil.toSystemIndependentName(name);
+    final int index = systemIndependentName.lastIndexOf("/");
+    if (index > 0) {
+      systemIndependentName = systemIndependentName.substring(index + 1);
+    }
+    File resourceFile = new File(resourceRoot, name);
+    File fileInProject = new File(taskDir.getPath(), systemIndependentName);
+    FileUtil.copy(resourceFile, fileInProject);
+  }
+
+  public void drawAllWindows(Editor editor) {
+    for (TaskWindow taskWindow : taskWindows) {
+      taskWindow.draw(editor, false, false);
+    }
+  }
+
+
+  /**
+   * @param pos position in editor
+   * @return task window located in specified position or null if there is no task window in this position
+   */
+  @Nullable
+  public TaskWindow getTaskWindow(@NotNull final Document document, @NotNull final LogicalPosition pos) {
+    int line = pos.line;
+    if (line >= document.getLineCount()) {
+      return null;
+    }
+    int column = pos.column;
+    int offset = document.getLineStartOffset(line) + column;
+    for (TaskWindow tw : taskWindows) {
+      if (tw.getLine() <= line) {
+        int twStartOffset = tw.getRealStartOffset(document);
+        final int length = tw.getLength() > 0 ? tw.getLength() : 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 : taskWindows) {
+      if (taskTaskWindow.getLine() >= startLine) {
+        taskTaskWindow.setLine(taskTaskWindow.getLine() + change);
+      }
+    }
+  }
+
+  /**
+   * Initializes state of task file
+   *
+   * @param task task which task file belongs to
+   */
+
+  public void init(final Task task, boolean isRestarted) {
+    myTask = task;
+    for (TaskWindow taskWindow : taskWindows) {
+      taskWindow.init(this, isRestarted);
+    }
+    Collections.sort(taskWindows);
+    for (int i = 0; i < taskWindows.size(); i++) {
+      taskWindows.get(i).setIndex(i);
+    }
+  }
+
+  /**
+   * @param index index of task file in list of task files of its task
+   */
+  public void setIndex(int index) {
+    myIndex = index;
+  }
+
+  /**
+   * Updates windows in specific line
+   *
+   * @param lineChange         change in line number
+   * @param line               line to be updated
+   * @param newEndOffsetInLine distance from line start to end of inserted fragment
+   * @param oldEndOffsetInLine distance from line start to end of changed fragment
+   */
+  public void updateLine(int lineChange, int line, int newEndOffsetInLine, int oldEndOffsetInLine) {
+    for (TaskWindow w : taskWindows) {
+      if ((w.getLine() == line) && (w.getStart() >= oldEndOffsetInLine)) {
+        int distance = w.getStart() - oldEndOffsetInLine;
+        if (lineChange != 0 || newEndOffsetInLine <= w.getStart()) {
+          w.setStart(distance + newEndOffsetInLine);
+          w.setLine(line + lineChange);
+        }
+      }
+    }
+  }
+
+  public static void copy(@NotNull final TaskFile source, @NotNull final TaskFile target) {
+    List<TaskWindow> sourceTaskWindows = source.getTaskWindows();
+    List<TaskWindow> windowsCopy = new ArrayList<TaskWindow>(sourceTaskWindows.size());
+    for (TaskWindow taskWindow : sourceTaskWindows) {
+      TaskWindow taskWindowCopy = new TaskWindow();
+      taskWindowCopy.setLine(taskWindow.getLine());
+      taskWindowCopy.setStart(taskWindow.getStart());
+      taskWindowCopy.setLength(taskWindow.getLength());
+      taskWindowCopy.setPossibleAnswer(taskWindow.getPossibleAnswer());
+      taskWindowCopy.setIndex(taskWindow.getIndex());
+      windowsCopy.add(taskWindowCopy);
+    }
+    target.setTaskWindows(windowsCopy);
+  }
+
+  public void setTaskWindows(List<TaskWindow> taskWindows) {
+    this.taskWindows = taskWindows;
+  }
+
+  public void setStatus(@NotNull final StudyStatus status, @NotNull final StudyStatus oldStatus) {
+    for (TaskWindow taskWindow : taskWindows) {
+      taskWindow.setStatus(status, oldStatus);
+    }
+  }
+
+  public void setUserCreated(boolean userCreated) {
+    myUserCreated = userCreated;
+  }
+
+  public boolean isUserCreated() {
+    return myUserCreated;
+  }
+
+  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);
+    }
+  }
+}
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
new file mode 100644
index 0000000..4fb112c
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/TaskWindow.java
@@ -0,0 +1,177 @@
+package com.jetbrains.python.edu.course;
+
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.markup.HighlighterLayer;
+import com.intellij.openapi.editor.markup.HighlighterTargetArea;
+import com.intellij.openapi.editor.markup.RangeHighlighter;
+import com.intellij.openapi.editor.markup.TextAttributes;
+import com.intellij.ui.JBColor;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Implementation of windows which user should type in
+ */
+
+
+public class TaskWindow implements Comparable, Stateful {
+
+  public int line = 0;
+  public int start = 0;
+  public String hint = "";
+  public String possibleAnswer = "";
+  public int length = 0;
+  private TaskFile myTaskFile;
+  public int myIndex = -1;
+  public int myInitialLine = -1;
+  public int myInitialStart = -1;
+  public int myInitialLength = -1;
+  public StudyStatus myStatus = StudyStatus.Unchecked;
+
+  public StudyStatus getStatus() {
+    return myStatus;
+  }
+
+  public void setStatus(StudyStatus status, StudyStatus oldStatus) {
+    myStatus = status;
+  }
+
+  public void setIndex(int index) {
+    myIndex = index;
+  }
+
+  public int getLength() {
+    return length;
+  }
+
+  public void setLength(int length) {
+    this.length = length;
+  }
+
+  public int getStart() {
+    return start;
+  }
+
+  public void setStart(int start) {
+    this.start = start;
+  }
+
+  public void setLine(int line) {
+    this.line = line;
+  }
+
+  public int getLine() {
+    return line;
+  }
+
+
+  /**
+   * Draw task window with color according to its status
+   */
+  public void draw(@NotNull final Editor editor, boolean drawSelection, boolean moveCaret) {
+    Document document = editor.getDocument();
+    if (!isValid(document)) {
+      return;
+    }
+    TextAttributes defaultTestAttributes =
+      EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.LIVE_TEMPLATE_ATTRIBUTES);
+    JBColor color = getColor();
+    int startOffset = document.getLineStartOffset(line) + start;
+    RangeHighlighter
+      rh = editor.getMarkupModel().addRangeHighlighter(startOffset, startOffset + length, HighlighterLayer.LAST + 1,
+                                                       new TextAttributes(defaultTestAttributes.getForegroundColor(),
+                                                                          defaultTestAttributes.getBackgroundColor(), color,
+                                                                          defaultTestAttributes.getEffectType(),
+                                                                          defaultTestAttributes.getFontType()),
+                                                       HighlighterTargetArea.EXACT_RANGE);
+    if (drawSelection) {
+      editor.getSelectionModel().setSelection(startOffset, startOffset + length);
+    }
+    if (moveCaret) {
+      editor.getCaretModel().moveToOffset(startOffset);
+    }
+    rh.setGreedyToLeft(true);
+    rh.setGreedyToRight(true);
+  }
+
+  public boolean isValid(@NotNull final Document document) {
+    boolean isLineValid = line < document.getLineCount() && line >= 0;
+    if (!isLineValid) return false;
+    boolean isStartValid = start >= 0 && start < document.getLineEndOffset(line);
+    boolean isLengthValid = (getRealStartOffset(document) + length) <= document.getTextLength();
+    return isLengthValid && isStartValid;
+  }
+
+  private JBColor getColor() {
+    if (myStatus == StudyStatus.Solved) {
+      return JBColor.GREEN;
+    }
+    if (myStatus == StudyStatus.Failed) {
+      return JBColor.RED;
+    }
+    return JBColor.BLUE;
+  }
+
+  public int getRealStartOffset(@NotNull final Document document) {
+    return document.getLineStartOffset(line) + start;
+  }
+
+  /**
+   * Initializes window
+   *
+   * @param file task file which window belongs to
+   */
+  public void init(final TaskFile file, boolean isRestarted) {
+    if (!isRestarted) {
+      myInitialLine = line;
+      myInitialLength = length;
+      myInitialStart = start;
+    }
+    myTaskFile = file;
+  }
+
+  public TaskFile getTaskFile() {
+    return myTaskFile;
+  }
+
+  @Override
+  public int compareTo(@NotNull Object o) {
+    TaskWindow taskWindow = (TaskWindow)o;
+    if (taskWindow.getTaskFile() != myTaskFile) {
+      throw new ClassCastException();
+    }
+    int lineDiff = line - taskWindow.line;
+    if (lineDiff == 0) {
+      return start - taskWindow.start;
+    }
+    return lineDiff;
+  }
+
+  /**
+   * Returns window to its initial state
+   */
+  public void reset() {
+    myStatus = StudyStatus.Unchecked;
+    line = myInitialLine;
+    start = myInitialStart;
+    length = myInitialLength;
+  }
+
+  public String getHint() {
+    return hint;
+  }
+
+  public String getPossibleAnswer() {
+    return possibleAnswer;
+  }
+
+  public void setPossibleAnswer(String possibleAnswer) {
+    this.possibleAnswer = possibleAnswer;
+  }
+
+  public int getIndex() {
+    return myIndex;
+  }
+}
\ No newline at end of file
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/course/UserTest.java b/python/edu/learn-python/src/com/jetbrains/python/edu/course/UserTest.java
new file mode 100644
index 0000000..8133e91
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/course/UserTest.java
@@ -0,0 +1,41 @@
+package com.jetbrains.python.edu.course;
+
+public class UserTest {
+  private String input;
+  private String output;
+  private StringBuilder myInputBuffer = new StringBuilder();
+  private StringBuilder myOutputBuffer =  new StringBuilder();
+  private boolean myEditable = false;
+
+  public String getInput() {
+    return input;
+  }
+
+  public void setInput(String input) {
+    this.input = input;
+  }
+
+  public String getOutput() {
+    return output;
+  }
+
+  public void setOutput(String output) {
+    this.output = output;
+  }
+
+  public StringBuilder getInputBuffer() {
+    return myInputBuffer;
+  }
+
+  public StringBuilder getOutputBuffer() {
+    return myOutputBuffer;
+  }
+
+  public boolean isEditable() {
+    return myEditable;
+  }
+
+  public void setEditable(boolean editable) {
+    myEditable = editable;
+  }
+}
diff --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
new file mode 100644
index 0000000..69c5acc
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyEditor.java
@@ -0,0 +1,347 @@
+package com.jetbrains.python.edu.editor;
+
+import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
+import com.intellij.ide.structureView.StructureViewBuilder;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorFactory;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
+import com.intellij.openapi.editor.impl.DocumentImpl;
+import com.intellij.openapi.fileEditor.*;
+import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
+import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl;
+import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.pom.Navigatable;
+import com.intellij.ui.HideableTitledPanel;
+import com.intellij.ui.JBColor;
+import com.intellij.util.ui.UIUtil;
+import com.jetbrains.python.edu.StudyDocumentListener;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.actions.*;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import icons.StudyIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+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.beans.PropertyChangeListener;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implementation of StudyEditor which has panel with special buttons and task text
+ * also @see {@link com.jetbrains.python.edu.editor.StudyFileEditorProvider}
+ */
+public class StudyEditor implements TextEditor {
+  private static final String TASK_TEXT_HEADER = "Task Text";
+  private final FileEditor myDefaultEditor;
+  private final JComponent myComponent;
+  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;
+
+  public JButton getCheckButton() {
+    return myCheckButton;
+  }
+
+  public JButton getPrevTaskButton() {
+    return myPrevTaskButton;
+  }
+
+  private static JButton addButton(@NotNull final JComponent parentComponent, String toolTipText, Icon icon) {
+    JButton newButton = new JButton();
+    newButton.setToolTipText(toolTipText);
+    newButton.setIcon(icon);
+    newButton.setSize(new Dimension(icon.getIconWidth(), icon.getIconHeight()));
+    parentComponent.add(newButton);
+    return newButton;
+  }
+
+  public static void addDocumentListener(@NotNull final Document document, @NotNull final StudyDocumentListener listener) {
+    myDocumentListeners.put(document, listener);
+  }
+
+  @Nullable
+  public static StudyDocumentListener getListener(@NotNull final Document document) {
+    return myDocumentListeners.get(document);
+  }
+
+  public StudyEditor(@NotNull final Project project, @NotNull final VirtualFile file) {
+    myProject = project;
+    myDefaultEditor = TextEditorProvider.getInstance().createEditor(myProject, file);
+    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();
+      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);
+      studyPanel.add(studyButtonPanel);
+      myComponent.add(studyPanel, BorderLayout.NORTH);
+    }
+  }
+
+  private static void initializeTaskText(JPanel studyPanel, @Nullable String taskText) {
+    JTextPane taskTextPane = new JTextPane();
+    taskTextPane.setContentType("text/html");
+    taskTextPane.setEditable(false);
+    taskTextPane.setText(taskText);
+    EditorColorsScheme editorColorsScheme = EditorColorsManager.getInstance().getGlobalScheme();
+    int fontSize = editorColorsScheme.getEditorFontSize();
+    String fontName = editorColorsScheme.getEditorFontName();
+    setJTextPaneFont(taskTextPane, new Font(fontName, Font.PLAIN, fontSize), JBColor.BLACK);
+    taskTextPane.setBackground(UIUtil.getPanelBackground());
+    taskTextPane.setBorder(new EmptyBorder(15, 20, 0, 100));
+    HideableTitledPanel taskTextPanel = new HideableTitledPanel(TASK_TEXT_HEADER, taskTextPane, true);
+    taskTextPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
+    studyPanel.add(taskTextPanel);
+  }
+
+  private static void setJTextPaneFont(JTextPane jtp, Font font, Color c) {
+    MutableAttributeSet attrs = jtp.getInputAttributes();
+    StyleConstants.setFontFamily(attrs, font.getFamily());
+    StyleConstants.setFontSize(attrs, font.getSize());
+    StyleConstants.setItalic(attrs, (font.getStyle() & Font.ITALIC) != 0);
+    StyleConstants.setBold(attrs, (font.getStyle() & Font.BOLD) != 0);
+    StyleConstants.setForeground(attrs, c);
+    StyledDocument doc = jtp.getStyledDocument();
+    doc.setCharacterAttributes(0, doc.getLength() + 1, attrs, false);
+  }
+
+  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);
+    if (!taskFile.getTask().getUserTests().isEmpty()) {
+      JButton runButton = addButton(taskActionsPanel, "Run", StudyIcons.Run);
+      runButton.addActionListener(new ActionListener() {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+          StudyRunAction studyRunAction = (StudyRunAction)ActionManager.getInstance().getAction("StudyRunAction");
+          studyRunAction.run(myProject);
+        }
+      });
+      JButton watchInputButton = addButton(taskActionsPanel, "Watch test input", StudyIcons.WatchInput);
+      watchInputButton.addActionListener(new ActionListener() {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+          StudyEditInputAction studyEditInputAction = (StudyEditInputAction)ActionManager.getInstance().getAction("WatchInputAction");
+          studyEditInputAction.showInput(myProject);
+        }
+      });
+    }
+    myCheckButton.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+        StudyCheckAction studyCheckAction = (StudyCheckAction)ActionManager.getInstance().getAction("CheckAction");
+        studyCheckAction.check(myProject);
+      }
+    });
+
+    myNextTaskButton.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+        StudyNextStudyTaskAction studyNextTaskAction = (StudyNextStudyTaskAction)ActionManager.getInstance().getAction("NextTaskAction");
+        studyNextTaskAction.navigateTask(myProject);
+      }
+    });
+    myPrevTaskButton.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+        StudyPreviousStudyTaskAction
+          prevTaskAction = (StudyPreviousStudyTaskAction)ActionManager.getInstance().getAction("PreviousTaskAction");
+        prevTaskAction.navigateTask(myProject);
+      }
+    });
+    myRefreshButton.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+        StudyRefreshTaskAction studyRefreshTaskAction = (StudyRefreshTaskAction)ActionManager.getInstance().getAction("RefreshTaskAction");
+        studyRefreshTaskAction.refresh(myProject);
+      }
+    });
+  }
+
+  public JButton getNextTaskButton() {
+    return myNextTaskButton;
+  }
+
+  public JButton getRefreshButton() {
+    return myRefreshButton;
+  }
+
+  FileEditor getDefaultEditor() {
+    return myDefaultEditor;
+  }
+
+  @NotNull
+  @Override
+  public JComponent getComponent() {
+    return myComponent;
+  }
+
+  @Nullable
+  @Override
+  public JComponent getPreferredFocusedComponent() {
+    return myDefaultEditor.getPreferredFocusedComponent();
+  }
+
+  @NotNull
+  @Override
+  public String getName() {
+    return "Study Editor";
+  }
+
+  @NotNull
+  @Override
+  public FileEditorState getState(@NotNull FileEditorStateLevel level) {
+    return myDefaultEditor.getState(level);
+  }
+
+  @Override
+  public void setState(@NotNull FileEditorState state) {
+    myDefaultEditor.setState(state);
+  }
+
+  @Override
+  public boolean isModified() {
+    return myDefaultEditor.isModified();
+  }
+
+  @Override
+  public boolean isValid() {
+    return myDefaultEditor.isValid();
+  }
+
+  @Override
+  public void selectNotify() {
+    myDefaultEditor.selectNotify();
+  }
+
+  @Override
+  public void deselectNotify() {
+    myDefaultEditor.deselectNotify();
+  }
+
+  @Override
+  public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
+    myDefaultEditor.addPropertyChangeListener(listener);
+  }
+
+  @Override
+  public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
+    myDefaultEditor.removePropertyChangeListener(listener);
+  }
+
+  @Nullable
+  @Override
+  public BackgroundEditorHighlighter getBackgroundHighlighter() {
+    return myDefaultEditor.getBackgroundHighlighter();
+  }
+
+  @Nullable
+  @Override
+  public FileEditorLocation getCurrentLocation() {
+    return myDefaultEditor.getCurrentLocation();
+  }
+
+  @Nullable
+  @Override
+  public StructureViewBuilder getStructureViewBuilder() {
+    return myDefaultEditor.getStructureViewBuilder();
+  }
+
+  @Override
+  public void dispose() {
+    Disposer.dispose(myDefaultEditor);
+  }
+
+  @Nullable
+  @Override
+  public <T> T getUserData(@NotNull Key<T> key) {
+    return myDefaultEditor.getUserData(key);
+  }
+
+  @Override
+  public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
+    myDefaultEditor.putUserData(key, value);
+  }
+
+
+  @Nullable
+  public static StudyEditor getSelectedStudyEditor(@NotNull final Project project) {
+    try {
+      FileEditor fileEditor = FileEditorManagerEx.getInstanceEx(project).getSplitters().getCurrentWindow().
+        getSelectedEditor().getSelectedEditorWithProvider().getFirst();
+      if (fileEditor instanceof StudyEditor) {
+        return (StudyEditor)fileEditor;
+      }
+    } catch (Exception e) {
+      return null;
+    }
+    return null;
+  }
+
+  @Nullable
+  public static Editor getSelectedEditor(@NotNull final Project project) {
+    StudyEditor studyEditor = getSelectedStudyEditor(project);
+    if (studyEditor != null) {
+      FileEditor defaultEditor = studyEditor.getDefaultEditor();
+      if (defaultEditor instanceof PsiAwareTextEditorImpl) {
+        return ((PsiAwareTextEditorImpl)defaultEditor).getEditor();
+      }
+    }
+    return null;
+  }
+
+  public static void removeListener(Document document) {
+    myDocumentListeners.remove(document);
+  }
+
+  @NotNull
+  @Override
+  public Editor getEditor() {
+    if (myDefaultEditor instanceof TextEditor)
+      return ((TextEditor)myDefaultEditor).getEditor();
+    return EditorFactory.getInstance().createViewer(new DocumentImpl(""), myProject);
+  }
+
+  @Override
+  public boolean canNavigateTo(@NotNull Navigatable navigatable) {
+    if (myDefaultEditor instanceof TextEditor) {
+      ((TextEditor)myDefaultEditor).canNavigateTo(navigatable);
+    }
+    return false;
+  }
+
+  @Override
+  public void navigateTo(@NotNull Navigatable navigatable) {
+    if (myDefaultEditor instanceof TextEditor) {
+      ((TextEditor)myDefaultEditor).navigateTo(navigatable);
+    }
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyFileEditorProvider.java b/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyFileEditorProvider.java
new file mode 100644
index 0000000..631b5a9
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/editor/StudyFileEditorProvider.java
@@ -0,0 +1,64 @@
+package com.jetbrains.python.edu.editor;
+
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorPolicy;
+import com.intellij.openapi.fileEditor.FileEditorProvider;
+import com.intellij.openapi.fileEditor.FileEditorState;
+import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.course.TaskFile;
+
+/**
+ * User: lia
+ * Date: 10.05.14
+ * Time: 12:45
+ */
+class StudyFileEditorProvider implements FileEditorProvider, DumbAware {
+  static final private String EDITOR_TYPE_ID = "StudyEditor";
+  final private FileEditorProvider defaultTextEditorProvider = TextEditorProvider.getInstance();
+
+  @Override
+  public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
+    TaskFile taskFile = StudyTaskManager.getInstance(project).getTaskFile(file);
+    return taskFile != null && !taskFile.isUserCreated();
+  }
+
+  @NotNull
+  @Override
+  public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) {
+    return new StudyEditor(project, file);
+  }
+
+  @Override
+  public void disposeEditor(@NotNull FileEditor editor) {
+    defaultTextEditorProvider.disposeEditor(editor);
+  }
+
+  @NotNull
+  @Override
+  public FileEditorState readState(@NotNull Element sourceElement, @NotNull Project project, @NotNull VirtualFile file) {
+    return defaultTextEditorProvider.readState(sourceElement, project, file);
+  }
+
+  @Override
+  public void writeState(@NotNull FileEditorState state, @NotNull Project project, @NotNull Element targetElement) {
+    defaultTextEditorProvider.writeState(state, project, targetElement);
+  }
+
+  @NotNull
+  @Override
+  public String getEditorTypeId() {
+    return EDITOR_TYPE_ID;
+  }
+
+  @NotNull
+  @Override
+  public FileEditorPolicy getPolicy() {
+    return FileEditorPolicy.HIDE_DEFAULT_EDITOR;
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java
new file mode 100644
index 0000000..abf648c
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java
@@ -0,0 +1,112 @@
+package com.jetbrains.python.edu.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.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.ui.JBColor;
+import com.intellij.ui.SimpleTextAttributes;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.*;
+import icons.StudyIcons;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class StudyDirectoryNode extends PsiDirectoryNode {
+  private final PsiDirectory myValue;
+  private final Project myProject;
+
+  public StudyDirectoryNode(@NotNull final Project project,
+                            PsiDirectory value,
+                            ViewSettings viewSettings) {
+    super(project, value, viewSettings);
+    myValue = value;
+    myProject = project;
+  }
+
+  @Override
+  protected void updateImpl(PresentationData data) {
+    data.setIcon(StudyIcons.Unchecked);
+    String valueName = myValue.getName();
+    StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(myProject);
+    Course course = studyTaskManager.getCourse();
+    if (course == null) {
+      return;
+    }
+    if (valueName.equals(myProject.getName())) {
+      data.clearText();
+      data.addText(course.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, JBColor.BLUE));
+      data.addText(" (" + valueName + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES);
+      return;
+    }
+    if (valueName.contains(Task.TASK_DIR)) {
+      TaskFile file = null;
+      for (PsiElement child : myValue.getChildren()) {
+        VirtualFile virtualFile = child.getContainingFile().getVirtualFile();
+        file = studyTaskManager.getTaskFile(virtualFile);
+        if (file != null) {
+          break;
+        }
+      }
+      if (file != null) {
+        Task task = file.getTask();
+        setStudyAttributes(task, data, task.getName());
+      }
+    }
+    if (valueName.contains(Lesson.LESSON_DIR)) {
+      int lessonIndex = Integer.parseInt(valueName.substring(Lesson.LESSON_DIR.length())) - 1;
+      Lesson lesson = course.getLessons().get(lessonIndex);
+      setStudyAttributes(lesson, data, lesson.getName());
+    }
+
+    if (valueName.contains(Course.PLAYGROUND_DIR)) {
+      if (myValue.getParent() != null) {
+        if (!myValue.getParent().getName().contains(Course.PLAYGROUND_DIR)) {
+          data.setPresentableText(Course.PLAYGROUND_DIR);
+          data.setIcon(StudyIcons.Playground);
+          return;
+        }
+      }
+    }
+    data.setPresentableText(valueName);
+  }
+
+  @Override
+  public int getTypeSortWeight(boolean sortByType) {
+    String name = myValue.getName();
+    if (name.contains(Lesson.LESSON_DIR) || name.contains(Task.TASK_DIR)) {
+      String logicalName = name.contains(Lesson.LESSON_DIR) ? Lesson.LESSON_DIR : Task.TASK_DIR;
+      return StudyUtils.getIndex(name, logicalName) + 1;
+    }
+    return name.contains(Course.PLAYGROUND_DIR) ? 0 : 3;
+  }
+
+  private static void setStudyAttributes(Stateful stateful, PresentationData data, String additionalName) {
+    StudyStatus taskStatus = stateful.getStatus();
+    switch (taskStatus) {
+      case Unchecked: {
+        updatePresentation(data, additionalName, JBColor.blue, StudyIcons.Unchecked);
+        break;
+      }
+      case Solved: {
+        updatePresentation(data, additionalName, new JBColor(new Color(0, 134, 0), new Color(98, 150, 85)), StudyIcons.Checked);
+        break;
+      }
+      case Failed: {
+        updatePresentation(data, additionalName, JBColor.RED, StudyIcons.Failed);
+      }
+    }
+  }
+
+  private static void updatePresentation(PresentationData data, String additionalName, JBColor color, Icon icon) {
+    data.clearText();
+    data.addText(additionalName, new SimpleTextAttributes(Font.PLAIN, color));
+    data.setIcon(icon);
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyTreeStructureProvider.java b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyTreeStructureProvider.java
new file mode 100644
index 0000000..e301bc3
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyTreeStructureProvider.java
@@ -0,0 +1,83 @@
+package com.jetbrains.python.edu.projectView;
+
+import com.intellij.ide.projectView.TreeStructureProvider;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.course.Course;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class StudyTreeStructureProvider 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 nodeValue = (PsiDirectory)node.getValue();
+          if (!nodeValue.getName().contains(Task.USER_TESTS)) {
+            StudyDirectoryNode newNode = new StudyDirectoryNode(project, nodeValue, settings);
+            nodes.add(newNode);
+          }
+        }
+        else {
+          if (parent instanceof StudyDirectoryNode) {
+            if (node instanceof PsiFileNode) {
+              PsiFileNode psiFileNode = (PsiFileNode)node;
+              VirtualFile virtualFile = psiFileNode.getVirtualFile();
+              if (virtualFile == null) {
+                return nodes;
+              }
+              TaskFile taskFile = StudyTaskManager.getInstance(project).getTaskFile(virtualFile);
+              if (taskFile != null) {
+                nodes.add(node);
+              }
+              String parentName = parent.getName();
+              if (parentName != null) {
+                if (parentName.equals(Course.PLAYGROUND_DIR)) {
+                  nodes.add(node);
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+    return nodes;
+  }
+
+  private static boolean needModify(AbstractTreeNode parent) {
+    Project project = parent.getProject();
+    if (project != null) {
+      StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project);
+      if (studyTaskManager.getCourse() == null) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Nullable
+  @Override
+  public Object getData(Collection<AbstractTreeNode> selected, String dataName) {
+    return null;
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyCondition.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyCondition.java
new file mode 100644
index 0000000..5add6c9
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyCondition.java
@@ -0,0 +1,25 @@
+package com.jetbrains.python.edu.ui;
+
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Condition;
+import com.jetbrains.python.edu.StudyTaskManager;
+
+/**
+ * author: liana
+ * data: 7/29/14.
+ */
+public class StudyCondition implements Condition, DumbAware {
+  public static boolean VALUE = false;
+  @Override
+  public boolean value(Object o) {
+    if (o instanceof Project) {
+      Project project = (Project) o;
+      StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+      if (taskManager.getCourse() != null) {
+        VALUE = true;
+      }
+    }
+    return false;
+  }
+}
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
new file mode 100644
index 0000000..133c38d
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.form
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.edu.ui.StudyNewProjectPanel">
+  <grid id="27dc6" binding="myContentPanel" layout-manager="GridLayoutManager" row-count="2" 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>
+      <grid id="54488" layout-manager="GridLayoutManager" row-count="2" 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="1" column="1" row-span="1" col-span="3" vsize-policy="2" hsize-policy="4" anchor="0" fill="3" indent="0" use-parent-layout="false">
+            <minimum-size width="-1" height="60"/>
+            <preferred-size width="-1" height="60"/>
+          </grid>
+        </constraints>
+        <properties/>
+        <border type="line">
+          <color color="-6709600"/>
+        </border>
+        <children>
+          <component id="213f6" class="javax.swing.JLabel" binding="myAuthorLabel">
+            <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="d754d" class="javax.swing.JLabel" binding="myDescriptionLabel">
+            <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"/>
+            </constraints>
+            <properties>
+              <text value=""/>
+            </properties>
+          </component>
+        </children>
+      </grid>
+      <component id="6c40c" class="javax.swing.JLabel">
+        <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>
+        </constraints>
+        <properties>
+          <font/>
+          <horizontalTextPosition value="0"/>
+          <text value="Courses:"/>
+        </properties>
+      </component>
+      <component id="21ac6" class="javax.swing.JComboBox" binding="myCoursesComboBox">
+        <constraints>
+          <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties/>
+      </component>
+      <component id="5c614" class="com.intellij.openapi.ui.FixedSizeButton" binding="myBrowseButton">
+        <constraints>
+          <grid row="0" column="2" 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="25"/>
+          </grid>
+        </constraints>
+        <properties/>
+      </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>
+        </constraints>
+        <properties>
+          <hideActionText value="false"/>
+          <text value=""/>
+          <toolTipText value="Refresh course list"/>
+          <verticalAlignment value="1"/>
+          <verticalTextPosition value="1"/>
+        </properties>
+      </component>
+    </children>
+  </grid>
+</form>
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
new file mode 100644
index 0000000..0f1ec08
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyNewProjectPanel.java
@@ -0,0 +1,196 @@
+package com.jetbrains.python.edu.ui;
+
+import com.intellij.facet.ui.FacetValidatorsManager;
+import com.intellij.facet.ui.ValidationResult;
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.vfs.VirtualFile;
+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;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * author: liana
+ * data: 7/31/14.
+ */
+public class StudyNewProjectPanel{
+  private Set<CourseInfo> myAvailableCourses = new HashSet<CourseInfo>();
+  private JComboBox myCoursesComboBox;
+  private JButton myBrowseButton;
+  private JButton myRefreshButton;
+  private JPanel myContentPanel;
+  private JLabel myAuthorLabel;
+  private JLabel myDescriptionLabel;
+  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";
+  private FacetValidatorsManager myValidationManager;
+
+  public StudyNewProjectPanel(StudyDirectoryProjectGenerator generator) {
+    myGenerator = generator;
+    Map<CourseInfo, File> courses = myGenerator.getCourses();
+    if (courses.isEmpty()) {
+      setError(CONNECTION_ERROR);
+    }
+    else {
+      myAvailableCourses = courses.keySet();
+      for (CourseInfo courseInfo : myAvailableCourses) {
+        myCoursesComboBox.addItem(courseInfo);
+      }
+      myAuthorLabel.setText("Author: " + StudyUtils.getFirst(myAvailableCourses).getAuthor());
+      myDescriptionLabel.setText(StudyUtils.getFirst(myAvailableCourses).getDescription());
+      //setting the first course in list as selected
+      myGenerator.setSelectedCourse(StudyUtils.getFirst(myAvailableCourses));
+      setOK();
+    }
+    initListeners();
+    myRefreshButton.setVisible(true);
+    myRefreshButton.setIcon(StudyIcons.Refresh);
+  }
+
+  private void initListeners() {
+
+    final FileChooserDescriptor fileChooser = new FileChooserDescriptor(true, false, false, true, false, false) {
+      @Override
+      public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
+        return file.isDirectory() || StudyUtils.isZip(file.getName());
+      }
+
+      @Override
+      public boolean isFileSelectable(VirtualFile file) {
+        return StudyUtils.isZip(file.getName());
+      }
+    };
+    myBrowseButton.addActionListener(new ActionListener() {
+      @Override
+      public void actionPerformed(ActionEvent e) {
+        FileChooser.chooseFile(fileChooser, null, null,
+                               new Consumer<VirtualFile>() {
+                                 @Override
+                                 public void consume(VirtualFile file) {
+                                   String fileName = file.getPath();
+                                   int oldSize = myAvailableCourses.size();
+                                   CourseInfo courseInfo = myGenerator.addLocalCourse(fileName);
+                                   if (courseInfo != null)  {
+                                     if (oldSize != myAvailableCourses.size()) {
+                                       myCoursesComboBox.addItem(courseInfo);
+                                     }
+                                     myCoursesComboBox.setSelectedItem(courseInfo);
+                                     setOK();
+                                   }
+                                   else {
+                                     setError(INVALID_COURSE);
+                                     myCoursesComboBox.removeAllItems();
+                                     myCoursesComboBox.addItem(CourseInfo.INVALID_COURSE);
+                                     for (CourseInfo course : myAvailableCourses) {
+                                       myCoursesComboBox.addItem(course);
+                                     }
+                                     myCoursesComboBox.setSelectedItem(CourseInfo.INVALID_COURSE);
+                                   }
+                                 }
+                               });
+      }
+    });
+    myRefreshButton.addActionListener(new RefreshActionListener());
+    myCoursesComboBox.addActionListener(new CourseSelectedListener());
+  }
+
+  private void setError(String errorMessage) {
+    myGenerator.setValidationResult(new ValidationResult(errorMessage));
+    if (myValidationManager != null) {
+      myValidationManager.validate();
+    }
+  }
+
+  private void setOK() {
+    myGenerator.setValidationResult(ValidationResult.OK);
+    if (myValidationManager != null) {
+      myValidationManager.validate();
+    }
+  }
+
+  public JPanel getContentPanel() {
+    return myContentPanel;
+  }
+
+  public void registerValidators(final FacetValidatorsManager manager) {
+    myValidationManager = manager;
+  }
+
+
+  /**
+   * Handles refreshing courses
+   * Old courses added to new courses only if their
+   * meta file still exists in local file system
+   */
+  private class RefreshActionListener implements ActionListener {
+    @Override
+    public void actionPerformed(ActionEvent e) {
+      myGenerator.downloadAndUnzip(true);
+      Map<CourseInfo, File> downloadedCourses = myGenerator.loadCourses();
+      if (downloadedCourses.isEmpty()) {
+        setError(CONNECTION_ERROR);
+        return;
+      }
+      Map<CourseInfo, File> oldCourses = myGenerator.getLoadedCourses();
+      Map<CourseInfo, File> newCourses = new HashMap<CourseInfo, File>();
+      for (Map.Entry<CourseInfo, File> course : oldCourses.entrySet()) {
+        File courseFile = course.getValue();
+        if (courseFile.exists()) {
+          newCourses.put(course.getKey(), courseFile);
+        }
+      }
+      for (Map.Entry<CourseInfo, File> course : downloadedCourses.entrySet()) {
+        CourseInfo courseName = course.getKey();
+        if (newCourses.get(courseName) == null) {
+          newCourses.put(courseName, course.getValue());
+        }
+      }
+      myCoursesComboBox.removeAllItems();
+
+      for (CourseInfo courseInfo : newCourses.keySet()) {
+        myCoursesComboBox.addItem(courseInfo);
+      }
+      myGenerator.setSelectedCourse(StudyUtils.getFirst(newCourses.keySet()));
+
+      myGenerator.setCourses(newCourses);
+      myAvailableCourses = newCourses.keySet();
+      myGenerator.flushCache();
+    }
+  }
+
+
+  /**
+   * Handles selecting course in combo box
+   * Sets selected course in combo box as selected in
+   * {@link StudyNewProjectPanel#myGenerator}
+   */
+  private class CourseSelectedListener implements ActionListener {
+    @Override
+    public void actionPerformed(ActionEvent e) {
+      JComboBox cb = (JComboBox)e.getSource();
+      CourseInfo selectedCourse = (CourseInfo)cb.getSelectedItem();
+      if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) {
+        myAuthorLabel.setText("");
+        myDescriptionLabel.setText("");
+        return;
+      }
+      myAuthorLabel.setText("Author: " + selectedCourse.getAuthor());
+      myCoursesComboBox.removeItem(CourseInfo.INVALID_COURSE);
+      myDescriptionLabel.setText(selectedCourse.getDescription());
+      myGenerator.setSelectedCourse(selectedCourse);
+      setOK();
+    }
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyProgressBar.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyProgressBar.java
new file mode 100644
index 0000000..97fa00d
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyProgressBar.java
@@ -0,0 +1,92 @@
+package com.jetbrains.python.edu.ui;
+
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.ui.GraphicsConfig;
+import com.intellij.ui.ColorUtil;
+import com.intellij.ui.Gray;
+import com.intellij.ui.JBColor;
+import com.intellij.util.ui.GraphicsUtil;
+import com.intellij.util.ui.UIUtil;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+public class StudyProgressBar extends JComponent implements DumbAware {
+  public static final Color BLUE = JBColor.BLUE;
+  private static final Color SHADOW1 = new JBColor(Gray._190, JBColor.border());
+  private static final Color SHADOW2 = Gray._105;
+  private static final int BRICK_WIDTH = 10;
+  private static final int BRICK_SPACE = 2;
+  private final int myHeight;
+  private final int myIndent;
+  private double myFraction = 0.0;
+  private Color myColor = BLUE;
+
+  public StudyProgressBar(double fraction, Color color, int height, int indent) {
+    myFraction = fraction;
+    myColor = color;
+    myHeight = height;
+    myIndent = indent;
+  }
+
+  private int getBricksToDraw(double fraction) {
+    int bricksTotal = (getWidth() - 8) / (BRICK_WIDTH + BRICK_SPACE);
+    return (int)(bricksTotal * fraction) + 1;
+  }
+
+  protected void paintComponent(Graphics g) {
+    final GraphicsConfig config = GraphicsUtil.setupAAPainting(g);
+    Graphics2D g2 = (Graphics2D)g;
+    if (myFraction > 1) {
+      myFraction = 1;
+    }
+
+    Dimension size = getSize();
+    double width = size.getWidth() - 2*myIndent;
+    g2.setPaint(UIUtil.getListBackground());
+    Rectangle2D rect = new Rectangle2D.Double(myIndent, 0, width, myHeight);
+    g2.fill(rect);
+
+    g2.setPaint(new JBColor(SHADOW1, JBColor.border()));
+    rect.setRect(myIndent, 0, width, myHeight);
+    int arcWidth = 5;
+    int arcHeight = 5;
+    g2.drawRoundRect(myIndent, 0, (int)width, myHeight, arcWidth, arcHeight);
+    g2.setPaint(SHADOW2);
+    g2.drawRoundRect(myIndent, 0, (int)width, myHeight, arcWidth, arcHeight);
+
+    int y_center = myHeight / 2;
+    int y_steps = myHeight / 2 - 3;
+    int alpha_step = y_steps > 0 ? (255 - 70) / y_steps : 255 - 70;
+    int x_offset = 4;
+
+    g.setClip(4 + myIndent, 3, (int)width - 6, myHeight - 4);
+
+    int bricksToDraw = myFraction == 0 ? 0 : getBricksToDraw(myFraction);
+    for (int i = 0; i < bricksToDraw; i++) {
+      g2.setPaint(myColor);
+      UIUtil.drawLine(g2, x_offset, y_center, x_offset + BRICK_WIDTH - 1, y_center);
+      for (int j = 0; j < y_steps; j++) {
+        Color color = ColorUtil.toAlpha(myColor, 255 - alpha_step * (j + 1));
+        g2.setPaint(color);
+        UIUtil.drawLine(g2, x_offset, y_center - 1 - j, x_offset + BRICK_WIDTH - 1, y_center - 1 - j);
+        if (!(y_center % 2 != 0 && j == y_steps - 1)) {
+          UIUtil.drawLine(g2, x_offset, y_center + 1 + j, x_offset + BRICK_WIDTH - 1, y_center + 1 + j);
+        }
+      }
+      g2.setColor(
+        ColorUtil.toAlpha(myColor, 255 - alpha_step * (y_steps / 2 + 1)));
+      g2.drawRect(x_offset, y_center - y_steps, BRICK_WIDTH - 1, myHeight - 7);
+      x_offset += BRICK_WIDTH + BRICK_SPACE;
+    }
+    config.restore();
+  }
+
+  @Override
+  public Dimension getMaximumSize() {
+    Dimension dimension = super.getMaximumSize();
+    dimension.height = myHeight + 10;
+    return dimension;
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyTestContentPanel.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyTestContentPanel.java
new file mode 100644
index 0000000..dee4fba
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyTestContentPanel.java
@@ -0,0 +1,67 @@
+package com.jetbrains.python.edu.ui;
+
+import com.intellij.ui.DocumentAdapter;
+import com.intellij.ui.components.JBScrollPane;
+import org.jetbrains.annotations.NotNull;
+import com.jetbrains.python.edu.course.UserTest;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.BadLocationException;
+import java.awt.*;
+
+public class StudyTestContentPanel extends JPanel {
+  public static final Dimension PREFERRED_SIZE = new Dimension(300, 200);
+  private static final Font HEADER_FONT = new Font("Arial", Font.BOLD, 16);
+  private final JTextArea myInputArea = new JTextArea();
+  private final JTextArea myOutputArea = new JTextArea();
+  public StudyTestContentPanel(UserTest userTest) {
+    this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+    initContentLabel("input", myInputArea);
+    myInputArea.getDocument().addDocumentListener(new BufferUpdater(userTest.getInputBuffer()));
+    myOutputArea.getDocument().addDocumentListener(new BufferUpdater(userTest.getOutputBuffer()));
+    initContentLabel("output", myOutputArea);
+    setEditable(userTest.isEditable());
+  }
+
+  private void initContentLabel(final String headerText, @NotNull final JTextArea contentArea) {
+    JLabel headerLabel = new JLabel(headerText);
+    headerLabel.setFont(HEADER_FONT);
+    this.add(headerLabel);
+    this.add(new JSeparator(SwingConstants.HORIZONTAL));
+    JScrollPane scroll = new JBScrollPane(contentArea);
+    scroll.setPreferredSize(PREFERRED_SIZE);
+    this.add(scroll);
+  }
+
+  private void setEditable(boolean isEditable) {
+    myInputArea.setEditable(isEditable);
+    myOutputArea.setEditable(isEditable);
+  }
+  public void addInputContent(final String content) {
+    myInputArea.setText(content);
+  }
+
+  public  void addOutputContent(final String content) {
+    myOutputArea.setText(content);
+  }
+
+  private class BufferUpdater extends DocumentAdapter {
+    private final StringBuilder myBuffer;
+
+    private BufferUpdater(StringBuilder buffer) {
+      myBuffer = buffer;
+    }
+
+    @Override
+    protected void textChanged(DocumentEvent e) {
+      myBuffer.delete(0, myBuffer.length());
+      try {
+        myBuffer.append(e.getDocument().getText(0, e.getDocument().getLength()));
+      }
+      catch (BadLocationException e1) {
+        e1.printStackTrace();
+      }
+    }
+  }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java
new file mode 100644
index 0000000..a553978
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java
@@ -0,0 +1,81 @@
+package com.jetbrains.python.edu.ui;
+
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowFactory;
+import com.intellij.ui.JBColor;
+import com.intellij.ui.content.Content;
+import com.intellij.ui.content.ContentFactory;
+import com.intellij.util.ui.UIUtil;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.course.Course;
+import com.jetbrains.python.edu.course.Lesson;
+import com.jetbrains.python.edu.course.LessonInfo;
+import com.jetbrains.python.edu.course.StudyStatus;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.List;
+
+public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware {
+  public static final String STUDY_TOOL_WINDOW = "Course Description";
+  JPanel contentPanel = new JPanel();
+
+  @Override
+  public void createToolWindowContent(@NotNull final Project project, @NotNull final ToolWindow toolWindow) {
+    if (StudyTaskManager.getInstance(project).getCourse() != null) {
+      contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.PAGE_AXIS));
+      contentPanel.add(Box.createRigidArea(new Dimension(10, 0)));
+      StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+      Course course = taskManager.getCourse();
+      if (course == null) {
+        return;
+      }
+      String courseName = UIUtil.toHtml("<h1>" + course.getName() + "</h1>", 10);
+      String description = UIUtil.toHtml(course.getDescription(), 5);
+      String author = taskManager.getCourse().getAuthor();
+      String authorLabel = UIUtil.toHtml("<b>Author: </b>" + author, 5);
+      contentPanel.add(new JLabel(courseName));
+      contentPanel.add(new JLabel(authorLabel));
+      contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+      contentPanel.add(new JLabel(description));
+
+      int taskNum = 0;
+      int taskSolved = 0;
+      int lessonsCompleted = 0;
+      List<Lesson> lessons = course.getLessons();
+      for (Lesson lesson : lessons) {
+        if (lesson.getStatus() == StudyStatus.Solved) {
+          lessonsCompleted++;
+        }
+        LessonInfo lessonInfo = lesson.getLessonInfo();
+        taskNum += lessonInfo.getTaskNum();
+        taskSolved += lessonInfo.getTaskSolved();
+      }
+      String completedLessons = String.format("%d of %d lessons completed", lessonsCompleted, course.getLessons().size());
+      String completedTasks = String.format("%d of %d tasks completed", taskSolved, taskNum);
+      String tasksLeft = String.format("%d of %d tasks left", taskNum - taskSolved, taskNum);
+      contentPanel.add(Box.createVerticalStrut(10));
+      addStatistics(completedLessons);
+      addStatistics(completedTasks);
+
+      double percent = (taskSolved * 100.0) / taskNum;
+      contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+      StudyProgressBar studyProgressBar = new StudyProgressBar(percent / 100, JBColor.GREEN, 40, 10);
+      contentPanel.add(studyProgressBar);
+      addStatistics(tasksLeft);
+      ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
+      Content content = contentFactory.createContent(contentPanel, "", true);
+      toolWindow.getContentManager().addContent(content);
+    }
+  }
+
+  private void addStatistics(String statistics) {
+    String labelText = UIUtil.toHtml(statistics, 5);
+    contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+    JLabel statisticLabel = new JLabel(labelText);
+    contentPanel.add(statisticLabel);
+  }
+}
diff --git a/python/edu/learn-python/testData/course.json b/python/edu/learn-python/testData/course.json
new file mode 100644
index 0000000..bff570a
--- /dev/null
+++ b/python/edu/learn-python/testData/course.json
@@ -0,0 +1,130 @@
+{
+  "name": "Python для начинающих",
+  "description": "Начальный курс по языку Python",
+  "lessons": [
+    {
+      "name": "Первые программа",
+      "task_list": [
+        {
+          "name": "Задание 1",
+          "text": "hello-text.html",
+          "test_file": "hello-tests.py",
+          "test_num": 1,
+          "task_files": {
+            "helloworld.py": {
+              "task_windows": [
+                {
+                  "line": 0,
+                  "start": 0,
+                  "text": "type operator",
+                  "hint": "hello-text.html",
+                  "possible_answer": "print"
+                },
+                {
+                  "line": 0,
+                  "start": 33,
+                  "text": "type your name",
+                  "hint": "empty_study.docs",
+                  "possible_answer": "Liana"
+                }
+              ]
+            }
+          }
+        },
+        {
+          "name": "Задание 2",
+          "text": "matchends-text.html",
+          "test_file": "matchends-test.py",
+          "test_num": 3,
+          "task_files": {
+            "match_ends.py": {
+              "task_windows": [
+                {
+                  "line": 1,
+                  "start": 43,
+                  "text": "condition",
+                  "hint": "empty_study.docs",
+                  "possible_answer": ">="
+                },
+                {
+                  "line": 1,
+                  "start": 61,
+                  "text": "index",
+                  "hint": "empty_study.docs",
+                  "possible_answer": "0"
+                },
+                {
+                  "line": 1,
+                  "start": 73,
+                  "text": "index",
+                  "hint": "empty_study.docs",
+                  "possible_answer": "-1"
+                },
+                {
+                  "line": 2,
+                  "start": 11,
+                  "text": "function",
+                  "hint": "list.docs",
+                  "possible_answer": "len"
+                }
+              ]
+            }
+          }
+        }
+      ]
+    },
+    {
+      "name": "Простые задачи",
+      "task_list": [
+        {
+          "name": "Задание 1",
+          "text": "sum-text.html",
+          "test_file": "sum_tests.py",
+          "test_num": 3,
+          "user_tests": [
+            {
+              "input": "sum-input.txt",
+              "output": "sum-output"
+            }
+          ],
+          "task_files": {
+            "sum.py": {
+              "task_windows": [
+                {
+                  "line": 4,
+                  "start": 15,
+                  "text": "получите из консоли имя файла",
+                  "hint": "argv.docs",
+                  "possible_answer": "sys.argv[1]"
+                },
+                {
+                  "line": 5,
+                  "start": 8,
+                  "text": "откройте файл на запись",
+                  "hint": "empty_study.docs",
+                  "possible_answer": "open(filename, 'r')"
+                },
+                {
+                  "line": 10,
+                  "start": 4,
+                  "text": "закройте файл",
+                  "hint": "empty_study.docs",
+                  "possible_answer": "f.close()"
+                },
+                {
+                  "line": 11,
+                  "start": 14,
+                  "text": "правильно проинициализируйте значение",
+                  "hint": "empty_study.docs",
+                  "possible_answer": "-sys.maxint"
+                }
+              ]
+            }
+          }
+        }
+
+      ]
+
+    }
+  ]
+}
\ No newline at end of file
diff --git a/python/edu/learn-python/tests/JsonParserTest.java b/python/edu/learn-python/tests/JsonParserTest.java
new file mode 100644
index 0000000..903f0a5
--- /dev/null
+++ b/python/edu/learn-python/tests/JsonParserTest.java
@@ -0,0 +1,37 @@
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import org.junit.Before;
+import org.junit.Test;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.Course;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * author: liana
+ * data: 7/4/14.
+ */
+public class JsonParserTest {
+  private Course myCourse = null;
+  @Before
+  public void setUp() throws FileNotFoundException {
+    Reader reader = new InputStreamReader(new FileInputStream("EDIDE/testData/course.json"));
+    Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+    myCourse = gson.fromJson(reader, Course.class);
+  }
+
+  @Test
+  public void testCourseLevel() {
+    assertEquals(myCourse.getName(), "Python для начинающих");
+    assertEquals(StudyUtils.getFirst(myCourse.getLessons().get(1).getTaskList().get(0).getUserTests()).getInput(), "sum-input.txt");
+    assertEquals(myCourse.getLessons().size(), 2);
+    assertEquals(myCourse.getLessons().get(0).getTaskList().size(), 2);
+    assertEquals(myCourse.getLessons().get(1).getTaskList().size(), 1);
+  }
+}
diff --git a/python/edu/main_pycharm_edu.iml b/python/edu/main_pycharm_edu.iml
index 2acdd87..12efe9b 100644
--- a/python/edu/main_pycharm_edu.iml
+++ b/python/edu/main_pycharm_edu.iml
@@ -11,11 +11,11 @@
     <orderEntry type="module" module-name="relaxng" />
     <orderEntry type="module" module-name="rest" />
     <orderEntry type="module" module-name="python-helpers" />
-    <orderEntry type="module" module-name="terminal" />
     <orderEntry type="module" module-name="python-ide-community" />
     <orderEntry type="module" module-name="platform-main" />
     <orderEntry type="module" module-name="ShortcutPromoter" />
     <orderEntry type="module" module-name="python-educational" />
+    <orderEntry type="module" module-name="learn-python" />
   </component>
 </module>
 
diff --git a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
index 46695ef..0fdf0d4 100644
--- a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
+++ b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
@@ -1,9 +1,9 @@
 <component>
   <company name="JetBrains s.r.o." url="http://www.jetbrains.com/?fromIDE"/>
-  <version major="3" minor="0" eap="true"/>
+  <version major="1" minor="0" eap="true"/>
   <build number="__BUILD_NUMBER__" date="__BUILD_DATE__"/>
-  <logo url="/pycharm_core_logo.png" textcolor="ffffff" progressColor="ffaa16" progressY="230" progressTailIcon="/community_progress_tail.png"/>
-  <about url="/pycharm_core_about.png" logoX="300" logoY="265" logoW="75" logoH="30" foreground="ffffff" linkColor="fca11a"/>
+  <logo url="/pycharm_edu_logo.png" textcolor="5a8179" progressColor="ffaa16" progressY="230" progressTailIcon="/community_progress_tail.png"/>
+  <about url="/pycharm_edu_about.png" logoX="300" logoY="265" logoW="75" logoH="30" foreground="5a8179" linkColor="fca11a"/>
   <icon size32="/PyCharmCore32.png" size16="/PyCharmCore16.png" size32opaque="/PyCharmCore32.png" size12="/PyCharmCore13.png" ico="PyCharmCore.ico"/>
   <package code="__PACKAGE_CODE__"/>
   <names product="PyCharm" fullname="PyCharm Educational Edition" script="charm"/>
diff --git a/python/edu/resources/pycharm_edu_about.png b/python/edu/resources/pycharm_edu_about.png
new file mode 100644
index 0000000..f3312ac
--- /dev/null
+++ b/python/edu/resources/pycharm_edu_about.png
Binary files differ
diff --git a/python/edu/resources/pycharm_edu_about@2x.png b/python/edu/resources/pycharm_edu_about@2x.png
new file mode 100644
index 0000000..2803e04
--- /dev/null
+++ b/python/edu/resources/pycharm_edu_about@2x.png
Binary files differ
diff --git a/python/edu/resources/pycharm_edu_logo.png b/python/edu/resources/pycharm_edu_logo.png
new file mode 100644
index 0000000..c8b69b3
--- /dev/null
+++ b/python/edu/resources/pycharm_edu_logo.png
Binary files differ
diff --git a/python/edu/resources/pycharm_edu_logo@2x.png b/python/edu/resources/pycharm_edu_logo@2x.png
new file mode 100644
index 0000000..6c186ad
--- /dev/null
+++ b/python/edu/resources/pycharm_edu_logo@2x.png
Binary files differ
diff --git a/python/edu/src/META-INF/PyCharmEduPlugin.xml b/python/edu/src/META-INF/PyCharmEduPlugin.xml
index 9adf03d..d5b2fdf 100644
--- a/python/edu/src/META-INF/PyCharmEduPlugin.xml
+++ b/python/edu/src/META-INF/PyCharmEduPlugin.xml
@@ -8,10 +8,16 @@
     </component>
   </application-components>
 
-  <xi:include href="/META-INF/pycharm-core.xml" xpointer="xpointer(/idea-plugin/*)"/>
+  <xi:include href="/META-INF/pycharm-community.xml" xpointer="xpointer(/idea-plugin/*)"/>
 
   <xi:include href="/META-INF/python-core.xml" xpointer="xpointer(/idea-plugin/*)"/>
 
+  <application-components>
+    <component>
+      <implementation-class>com.jetbrains.python.edu.PyCharmEduInitialConfigurator</implementation-class>
+      <headless-implementation-class/>
+    </component>
+  </application-components>
 
   <actions>
     <group overrides="true" class="com.intellij.openapi.actionSystem.EmptyActionGroup" id="ToolsMenu"/>
diff --git a/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java b/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
index 451c519..ecf3d79 100644
--- a/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
+++ b/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
@@ -25,10 +25,8 @@
 import com.intellij.ide.SelectInTarget;
 import com.intellij.ide.ui.UISettings;
 import com.intellij.ide.util.PropertiesComponent;
-import com.intellij.ide.util.TipDialog;
 import com.intellij.notification.EventLog;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.ModalityState;
 import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
 import com.intellij.openapi.extensions.Extensions;
 import com.intellij.openapi.extensions.ExtensionsArea;
@@ -41,8 +39,6 @@
 import com.intellij.openapi.project.ProjectManager;
 import com.intellij.openapi.project.ProjectManagerAdapter;
 import com.intellij.openapi.project.ex.ProjectManagerEx;
-import com.intellij.openapi.util.Disposer;
-import com.intellij.openapi.util.Ref;
 import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.wm.ToolWindowEP;
 import com.intellij.openapi.wm.ToolWindowId;
@@ -51,7 +47,6 @@
 import com.intellij.platform.PlatformProjectViewOpener;
 import com.intellij.psi.codeStyle.CodeStyleSettings;
 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
-import com.intellij.util.Alarm;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.messages.MessageBus;
 import com.jetbrains.python.PythonLanguage;
@@ -91,13 +86,15 @@
                                        RecentProjectsManagerBase recentProjectsManager) {
     if (!propertiesComponent.getBoolean(CONFIGURED, false)) {
       propertiesComponent.setValue(CONFIGURED, "true");
-      recentProjectsManager.loadState(new RecentProjectsManagerBase.State());
       propertiesComponent.setValue("toolwindow.stripes.buttons.info.shown", "true");
       UISettings.getInstance().HIDE_TOOL_STRIPES = false;
       uiSettings.SHOW_MEMORY_INDICATOR = false;
       uiSettings.SHOW_DIRECTORY_FOR_NON_UNIQUE_FILENAMES = true;
+      uiSettings.SHOW_MAIN_TOOLBAR = false;
       codeInsightSettings.REFORMAT_ON_PASTE = CodeInsightSettings.NO_REFORMAT;
 
+      GeneralSettings.getInstance().setShowTipsOnStartup(false);
+
       EditorSettingsExternalizable.getInstance().setVirtualSpace(false);
       final CodeStyleSettings settings = CodeStyleSettingsManager.getInstance().getCurrentSettings();
       settings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true;
@@ -116,23 +113,31 @@
           });
         }
       });
-      PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP = false;
+      PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP = true;
     }
-    bus.connect().subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener.Adapter() {
-      @Override
-      public void appFrameCreated(String[] commandLineArgs, @NotNull Ref<Boolean> willOpenProject) {
-        if (!propertiesComponent.isValueSet(DISPLAYED_PROPERTY)) {
-          GeneralSettings.getInstance().setShowTipsOnStartup(false);
-          showInitialConfigurationDialog();
-          propertiesComponent.setValue(DISPLAYED_PROPERTY, "true");
-        }
-      }
 
-      @Override
-      public void appStarting(Project projectFromCommandLine) {
-        patchKeymap();
-      }
-    });
+    if (!propertiesComponent.isValueSet(DISPLAYED_PROPERTY)) {
+
+      bus.connect().subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener.Adapter() {
+        @Override
+        public void welcomeScreenDisplayed() {
+
+          ApplicationManager.getApplication().invokeLater(new Runnable() {
+            @Override
+            public void run() {
+              if (!propertiesComponent.isValueSet(DISPLAYED_PROPERTY)) {
+                GeneralSettings.getInstance().setShowTipsOnStartup(false);
+                propertiesComponent.setValue(DISPLAYED_PROPERTY, "true");
+                showInitialConfigurationDialog();
+
+                patchKeymap();
+              }
+            }
+          });
+        }
+      });
+    }
+
     bus.connect().subscribe(ProjectManager.TOPIC, new ProjectManagerAdapter() {
       @Override
       public void projectOpened(final Project project) {
@@ -142,58 +147,16 @@
         }
 
         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) {
-        //          ToolWindowManager.getInstance(project).invokeLater(this);
-        //          return;
-        //        }
-        //        if (!propertiesComponent.isValueSet(INIT_DB_DIALOG_DISPLAYED)) {
-        //          ToolWindow toolWindow = DatabaseView.getDatabaseToolWindow(project);
-        //          if (toolWindow.getType() != ToolWindowType.SLIDING) {
-        //            toolWindow.activate(null);
-        //          }
-        //          propertiesComponent.setValue(INIT_DB_DIALOG_DISPLAYED, "true");
-        //          onFirstProjectOpened(project);
-        //        }
-        //      }
-        //    });
-        //  }
-        //});
       }
     });
   }
 
-  private static void onFirstProjectOpened(@NotNull final Project project) {
-    // show python console
-
-
-    GeneralSettings.getInstance().setShowTipsOnStartup(true);
-
-    // show tips once
-    final Alarm alarm = new Alarm(project);
-    alarm.addRequest(new Runnable() {
-      @Override
-      public void run() {
-        Disposer.dispose(alarm);
-        TipDialog.createForProject(project).show();
-      }
-    }, 2000, ModalityState.NON_MODAL);
-  }
-
   private static void patchRootAreaExtensions() {
     ExtensionsArea rootArea = Extensions.getArea(null);
 
     for (ToolWindowEP ep : Extensions.getExtensions(ToolWindowEP.EP_NAME)) {
-      if (ToolWindowId.FAVORITES_VIEW.equals(ep.id) || ToolWindowId.TODO_VIEW.equals(ep.id) || EventLog.LOG_TOOL_WINDOW_ID.equals(ep.id)) {
+      if (ToolWindowId.FAVORITES_VIEW.equals(ep.id) || ToolWindowId.TODO_VIEW.equals(ep.id) || EventLog.LOG_TOOL_WINDOW_ID.equals(ep.id)
+          || "Structure".equals(ep.id)) {
         rootArea.getExtensionPoint(ToolWindowEP.EP_NAME).unregisterExtension(ep);
       }
     }