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

Change-Id: Ie01af1d8710ec0ff51d90301bda1a18b0b5c0faf
diff --git a/python/IntelliLang-python/IntelliLang-python.iml b/python/IntelliLang-python/IntelliLang-python.iml
index a877c73..49ab883 100644
--- a/python/IntelliLang-python/IntelliLang-python.iml
+++ b/python/IntelliLang-python/IntelliLang-python.iml
@@ -4,6 +4,7 @@
     <exclude-output />
     <content url="file://$MODULE_DIR$">
       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
diff --git a/python/IntelliLang-python/src/META-INF/intellilang-python-support.xml b/python/IntelliLang-python/resources/META-INF/intellilang-python-support.xml
similarity index 91%
rename from python/IntelliLang-python/src/META-INF/intellilang-python-support.xml
rename to python/IntelliLang-python/resources/META-INF/intellilang-python-support.xml
index 44bab2c..c96f0b7 100644
--- a/python/IntelliLang-python/src/META-INF/intellilang-python-support.xml
+++ b/python/IntelliLang-python/resources/META-INF/intellilang-python-support.xml
@@ -3,7 +3,7 @@
 <idea-plugin version="2">
   <extensions defaultExtensionNs="org.intellij.intelliLang">
     <languageSupport implementation="com.jetbrains.python.intelliLang.PyLanguageInjectionSupport"/>
-    <injectionConfig config="pyInjections.xml"/>
+    <injectionConfig config="resources/pyInjections.xml"/>
   </extensions>
   <extensions defaultExtensionNs="com.intellij">
     <patterns.patternClass className="com.jetbrains.python.patterns.PythonPatterns" alias="py"/>
diff --git a/python/IntelliLang-python/resources/META-INF/plugin.xml b/python/IntelliLang-python/resources/META-INF/plugin.xml
new file mode 100644
index 0000000..6b2717a
--- /dev/null
+++ b/python/IntelliLang-python/resources/META-INF/plugin.xml
@@ -0,0 +1,16 @@
+<idea-plugin version="2" xmlns:xi="http://www.w3.org/2001/XInclude">
+  <name>Python IntelliLang</name>
+  <id>org.jetbrains.plugins.python-intelliLang</id>
+  <version>VERSION</version>
+  <description>This plugin enables language injections</description>
+  <vendor>JetBrains</vendor>
+
+  <depends>com.intellij.modules.python</depends>
+  <depends>org.intellij.intelliLang</depends>
+
+  <xi:include href="/META-INF/intellilang-python-support.xml" xpointer="xpointer(/idea-plugin/*)"/>
+
+  <extensions defaultExtensionNs="com.intellij">
+    <errorHandler implementation="com.intellij.diagnostic.ITNReporter"/>
+  </extensions>
+</idea-plugin>
diff --git a/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyLanguageInjectionSupport.java b/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyLanguageInjectionSupport.java
index dee16d2..7076878 100644
--- a/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyLanguageInjectionSupport.java
+++ b/python/IntelliLang-python/src/com/jetbrains/python/intelliLang/PyLanguageInjectionSupport.java
@@ -58,6 +58,6 @@
   @Nullable
   @Override
   public BaseInjection findCommentInjection(@NotNull PsiElement host, @Nullable Ref<PsiElement> commentRef) {
-    return null;
+    return super.findCommentInjection(host, commentRef);
   }
 }
diff --git a/python/IntelliLang-python/src/pyInjections.xml b/python/IntelliLang-python/src/resources/pyInjections.xml
similarity index 100%
rename from python/IntelliLang-python/src/pyInjections.xml
rename to python/IntelliLang-python/src/resources/pyInjections.xml
diff --git a/python/build/paths.nsi b/python/build/paths.nsi
index cf4f769..6baf096 100644
--- a/python/build/paths.nsi
+++ b/python/build/paths.nsi
@@ -2,5 +2,5 @@
 !define IMAGES_LOCATION ${COMMUNITY_DIR}\python\build\resources
 ;!define LICENSE_FILE ${BASE_DIR}\python\license\PyCharm_Preview_License
 !define PRODUCT_PROPERTIES_FILE ${BASE_DIR}\out\pycharmCE\layout\bin\idea.properties
-!define PRODUCT_VM_OPTIONS_NAME pycharm.exe.vmoptions
+!define PRODUCT_VM_OPTIONS_NAME pycharm*.exe.vmoptions
 !define PRODUCT_VM_OPTIONS_FILE ${BASE_DIR}\out\pycharmCE\win\bin\${PRODUCT_VM_OPTIONS_NAME}
diff --git a/python/build/pycharm64_community_launcher.properties b/python/build/pycharm64_community_launcher.properties
new file mode 100644
index 0000000..ff7b0ff
--- /dev/null
+++ b/python/build/pycharm64_community_launcher.properties
@@ -0,0 +1,3 @@
+IDS_JDK_ENV_VAR=PYCHARM_JDK_64
+IDS_JDK_ONLY=true
+IDS_VM_OPTIONS=-Didea.platform.prefix=PyCharmCore -Didea.no.jre.check=true -Didea.paths.selector=__PRODUCT_PATHS_SELECTOR__
diff --git a/python/build/pycharm_community_build.gant b/python/build/pycharm_community_build.gant
index 3b793ec..cfaaf33 100644
--- a/python/build/pycharm_community_build.gant
+++ b/python/build/pycharm_community_build.gant
@@ -162,11 +162,14 @@
   ant.echo(message: "PC-${buildNumber}", file: "${paths.distAll}/build.txt")
 
   def launcher = "${paths.distWin}/bin/pycharm.exe"
+  def launcher64 = "${paths.distWin}/bin/pycharm64.exe"
   List resourcePaths = ["$ch/community-resources/src",
     "$ch/platform/icons/src",
     "$pythonCommunityHome/resources"]
   buildWinLauncher("$ch", "$ch/bin/WinLauncher/WinLauncher.exe", launcher,
                    appInfo, "$pythonCommunityHome/build/pycharm_community_launcher.properties", system_selector, resourcePaths)
+  buildWinLauncher("$ch", "$ch/bin/WinLauncher/WinLauncher64.exe", launcher64,
+                   appInfo, "$pythonCommunityHome/build/pycharm64_community_launcher.properties", system_selector, resourcePaths)
 
   buildWinZip("${paths.artifacts}/pycharmPC-${buildNumber}.zip", [paths.distAll, paths.distWin])
 
@@ -328,8 +331,7 @@
   }
 
   winScripts(target, ch, "pycharm.bat", args)
-  winVMOptions(target, null, "pycharm.exe")
-
+  winVMOptions(target, null, "pycharm.exe", "pycharm64.exe")
   ant.copy(file: "$home/python/help/pycharmhelp.jar", todir: "$target/help", failonerror: false)
 }
 
diff --git a/python/edu/build/DMG_background.png b/python/edu/build/DMG_background.png
new file mode 100644
index 0000000..3b5a4b2
--- /dev/null
+++ b/python/edu/build/DMG_background.png
Binary files differ
diff --git a/python/edu/build/build.xml b/python/edu/build/build.xml
new file mode 100644
index 0000000..913237c
--- /dev/null
+++ b/python/edu/build/build.xml
@@ -0,0 +1,46 @@
+<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/paths.nsi b/python/edu/build/paths.nsi
new file mode 100644
index 0000000..910f2b3
--- /dev/null
+++ b/python/edu/build/paths.nsi
@@ -0,0 +1,6 @@
+; Installer images
+!define IMAGES_LOCATION ${COMMUNITY_DIR}\python\build\resources
+;!define LICENSE_FILE ${BASE_DIR}\python\license\PyCharm_Preview_License
+!define PRODUCT_PROPERTIES_FILE ${BASE_DIR}\out\pycharmEDU\layout\bin\idea.properties
+!define PRODUCT_VM_OPTIONS_NAME pycharm.exe.vmoptions
+!define PRODUCT_VM_OPTIONS_FILE ${BASE_DIR}\out\pycharmEDU\win\bin\${PRODUCT_VM_OPTIONS_NAME}
diff --git a/python/edu/build/plugin-list.txt b/python/edu/build/plugin-list.txt
new file mode 100644
index 0000000..ceaa608
--- /dev/null
+++ b/python/edu/build/plugin-list.txt
@@ -0,0 +1,11 @@
+svn4idea
+git4idea
+remote-servers-git
+github
+terminal
+IntelliLang
+IntelliLang-xml
+IntelliLang-js
+IntelliLang-python
+rest
+python-rest
diff --git a/python/edu/build/pycharm_edu_build.gant b/python/edu/build/pycharm_edu_build.gant
new file mode 100644
index 0000000..8c857c1
--- /dev/null
+++ b/python/edu/build/pycharm_edu_build.gant
@@ -0,0 +1,393 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+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")
+
+requireProperty("buildNumber", requireProperty("build.number", snapshot))
+
+setProperty("ch", "$home")
+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())
+
+//modules to compile
+setProperty("pluginFilter", new File("$pythonEduHome/build/plugin-list.txt").readLines())
+
+private List<String> pycharmPlatformApiModules() {
+  return [platformApiModules, "dom-openapi"].flatten()
+}
+
+
+private List pycharmImplementationModules() {   //modules to put into pycharm.jar
+  return [platformImplementationModules, "dom-impl", "python-community", "python-ide-community", "python-educational", "python-openapi", "python-psi-api",
+    "platform-main"].flatten()
+}
+
+private List modules() {
+  return [
+    "python-pydev", "colorSchemes", pycharmPlatformApiModules(), pycharmImplementationModules(), pluginFilter
+  ].flatten()
+}
+
+private List approvedJars() {
+  def normalizedHome = ch.replace('\\', '/')
+  def normalizedPythonHome = pythonCommunityHome.replace('\\', '/')
+  return ["$normalizedHome/lib/", "$normalizedPythonHome/lib/", "$normalizedHome/xml/relaxng/lib/"]
+}
+
+class Paths {
+  final sandbox
+  final distAll
+  final distWin
+  final distMac
+  final distUnix
+  final artifacts
+  final ideaSystem
+  final ideaConfig
+
+  def Paths(String home) {
+    sandbox = "$home/out/pycharmEDU"
+
+    distAll = "$sandbox/layout"
+    distWin = "$sandbox/win"
+    distMac = "$sandbox/mac"
+    distUnix = "$sandbox/unix"
+    artifacts = "$sandbox/artifacts"
+
+    ideaSystem = "$sandbox/system"
+    ideaConfig = "$sandbox/config"
+  }
+}
+
+setProperty("paths", new Paths(home))
+setProperty("buildName", "PE-$buildNumber")
+
+target('default': "Build artifacts") {
+
+  loadProject()
+
+  projectBuilder.stage("Cleaning up sandbox folder")
+
+  projectBuilder.targetFolder = "${paths.sandbox}/classes"
+  projectBuilder.dryRun = dryRun
+
+  if (!dryRun) {
+    forceDelete(paths.sandbox)
+    ant.mkdir(dir: paths.sandbox)
+  }
+
+  ant.tstamp() {
+    format(property: "todayYear", pattern: "yyyy")
+  }
+
+  ant.patternset(id: "resources.included") {
+    include(name: "**/*.properties")
+    include(name: "fileTemplates/**/*")
+    include(name: "inspectionDescriptions/**/*")
+    include(name: "intentionDescriptions/**/*")
+    include(name: "tips/**/*")
+    include(name: "search/**/*")
+  }
+
+  ant.patternset(id: "resources.excluded") {
+    exclude(name: "**/*.properties")
+    exclude(name: "fileTemplates/**/*")
+    exclude(name: "fileTemplates")
+    exclude(name: "inspectionDescriptions/**/*")
+    exclude(name: "inspectionDescriptions")
+    exclude(name: "intentionDescriptions/**/*")
+    exclude(name: "intentionDescriptions")
+    exclude(name: "tips/**/*")
+    exclude(name: "tips")
+  }
+
+  zipSources(home, paths.artifacts)
+
+  def usedJars = buildModulesAndCollectUsedJars(modules(), approvedJars(), ["/ant/"])
+
+  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)
+
+}
+
+public layoutEducational(String classesPath, Set usedJars) {
+  setProperty("pluginFilter", new File("$pythonEduHome/build/plugin-list.txt").readLines())
+
+  if (usedJars == null) {
+    usedJars = collectUsedJars(modules(), approvedJars(), ["/ant/"], null)
+  }
+
+  def appInfo = appInfoFile(classesPath)
+  def paths = new Paths(home)
+  buildSearchableOptions("${projectBuilder.moduleOutput(findModule("platform-resources"))}/search", [], {
+    projectBuilder.moduleRuntimeClasspath(findModule("main_pycharm_edu"), false).each {
+      ant.pathelement(location: it)
+    }
+  }, "-Didea.platform.prefix=PyCharmEdu -Didea.no.jre.check=true")
+
+  if (!dryRun) {
+    wireBuildDate("PE-${buildNumber}", appInfo)
+  }
+
+  Map args = [
+    buildNumber: "PE-${buildNumber}",
+    system_selector: system_selector,
+    ide_jvm_args: "-Didea.platform.prefix=PyCharmEdu -Didea.no.jre.check=true"]
+
+  LayoutInfo layoutInfo = layoutFull(args, paths.distAll, usedJars)
+  generateLicensesTable("$paths.artifacts/third-party-libraries.txt", layoutInfo.usedModules);
+
+  layoutWin(args, paths.distWin)
+  layoutUnix(args, paths.distUnix)
+  layoutMac(args, paths.distMac)
+
+  ant.echo(message: "PE-${buildNumber}", file: "${paths.distAll}/build.txt")
+
+  def launcher = "${paths.distWin}/bin/pycharm.exe"
+  List resourcePaths = ["$ch/community-resources/src",
+    "$ch/platform/icons/src",
+    "$pythonEduHome/resources"]
+  buildWinLauncher("$ch", "$ch/bin/WinLauncher/WinLauncher.exe", launcher,
+                   appInfo, "$pythonEduHome/build/pycharm_edu_launcher.properties", system_selector, resourcePaths)
+
+  buildWinZip("${paths.artifacts}/pycharmPE-${buildNumber}.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])
+
+  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)
+}
+
+private layoutPlugins(layouts) {
+  dir("plugins") {
+    layouts.layoutPlugin("rest")
+    layouts.layoutPlugin("python-rest")
+  }
+
+  layouts.layoutCommunityPlugins(ch)
+}
+
+private String appInfoFile(String classesPath) {
+  return "$classesPath/python-educational/idea/PyCharmEduApplicationInfo.xml"
+}
+
+private layoutFull(Map args, String target, Set usedJars) {
+  def openapiModules = pycharmPlatformApiModules()
+  def superLayouts = includeFile("$ch/build/scripts/layouts.gant")
+
+  reassignAltClickToMultipleCarets("$ch")
+  
+  def result = layout(target) {
+    dir("lib") {
+      jar("util.jar") {
+        module("util")
+        module("util-rt")
+      }
+
+      jar("openapi.jar") {
+        openapiModules.each { module it }
+      }
+
+      jar("annotations.jar") { module("annotations") }
+      jar("extensions.jar") { module("extensions") }
+
+      jar("pycharm.jar") {
+        pycharmImplementationModules().each {
+          module(it) {
+            exclude(name: "**/tips/**")
+          }
+        }
+      }
+
+      jar("pycharm-pydev.jar") {
+        module("python-pydev")
+      }
+
+      jar("bootstrap.jar") { module("bootstrap") }
+      jar("resources.jar") {
+        module("platform-resources")
+        module("colorSchemes")
+      }
+
+      jar("forms_rt.jar") {
+        module("forms_rt")
+      }
+
+      //noinspection GroovyAssignabilityCheck
+      jar([name: "resources_en.jar", duplicate: "preserve"]) {
+        // custom resources should go first
+        fileset(dir: "$pythonCommunityHome/resources") {
+          include(name: "**/tips/**")
+        }
+        module("platform-resources-en") {
+          ant.patternset {
+            exclude(name: "tips/images/switcher.png")
+            exclude(name: "tips/images/navigateToFilePath.gif")
+          }
+        }
+      }
+
+      jar("icons.jar") { module("icons") }
+      jar("boot.jar") { module("boot") }
+
+      usedJars.each {
+        fileset(file: it)
+      }
+
+      dir("libpty") {
+        fileset(dir: "$ch/lib/libpty") {
+          exclude(name: "*.txt")
+        }
+      }
+
+      dir("ext") {
+        fileset(dir: "$ch/lib") {
+          include(name: "cglib*.jar")
+        }
+      }
+
+      dir("src") {
+        fileset(dir: "$ch/lib/src") {
+          include(name: "trove4j_changes.txt")
+          include(name: "trove4j_src.jar")
+        }
+
+        jar("pycharm-pydev-src.zip") {
+          fileset(dir: "$pythonCommunityHome/pydevSrc")
+        }
+        jar("pycharm-openapi-src.zip") {
+          fileset(dir: "$pythonCommunityHome/openapi/src")
+          fileset(dir: "$pythonCommunityHome/psi-api/src")
+        }
+      }
+    }
+
+    dir("help") {
+      fileset(dir: "$home/python/help") {
+        include(name: "*.pdf")
+      }
+    }
+
+    dir("helpers") {
+      fileset(dir: "$pythonCommunityHome/helpers")
+    }
+
+    dir("license") {
+      fileset(dir: "$ch/license")
+      fileset(dir: "$ch") {
+        include(name: "LICENSE.txt")
+        include(name: "NOTICE.txt")
+      }
+    }
+
+    layoutPlugins(superLayouts)
+
+    dir("bin") {
+      fileset(dir: "$ch/bin") {
+        exclude(name: "appletviewer.policy")
+        include(name: "*.*")
+      }
+    }
+  }
+  patchPropertiesFile(target, args + [appendices: ["$home/build/conf/ideaJNC.properties"]])
+  return result
+}
+
+private layoutWin(Map args, String target) {
+  layout(target) {
+    dir("bin") {
+      fileset(dir: "$ch/bin/win") {
+        exclude(name: "breakgen*")
+      }
+    }
+
+    dir("skeletons") {
+      fileset(dir: "$pythonCommunityHome/skeletons") {
+        include(name: "skeletons-win*.zip")
+      }
+    }
+  }
+
+  winScripts(target, ch, "pycharm.bat", args)
+  winVMOptions(target, null, "pycharm.exe")
+
+  ant.copy(file: "$home/python/help/pycharmhelp.jar", todir: "$target/help", failonerror: false)
+}
+
+private layoutUnix(Map args, String target) {
+  layout(target) {
+    dir("bin") {
+      fileset(dir: "$ch/bin/linux") {
+        exclude(name: "libbreakgen*")
+      }
+    }
+  }
+
+  ant.copy(file: "$pythonCommunityHome/resources/PyCharmCore128.png", tofile: "$target/bin/pycharm.png")
+
+  unixScripts(target, ch, "pycharm.sh", args)
+  unixVMOptions(target, "pycharm")
+
+  ant.copy(file: "$home/python/help/pycharmhelp.jar", todir: "$target/help", failonerror: false)
+}
+
+private layoutMac(Map _args, String target) {
+  layout(target) {
+    dir("bin") {
+      fileset(dir: "$home/bin") {
+        include(name: "*.jnilib")
+      }
+    }
+
+    dir("skeletons") {
+      fileset(dir: "$pythonCommunityHome/skeletons") {
+        include(name: "skeletons-mac*.zip")
+      }
+    }
+  }
+
+  Map args = new HashMap(_args)
+  args.icns = "$pythonCommunityHome/resources/PyCharmCore.icns"
+  args.bundleIdentifier = "com.jetbrains.pycharm"
+  args.platform_prefix = "PyCharmEdu"
+  args.help_id = "PY"
+  args."idea.properties.path" = "${paths.distAll}/bin/idea.properties"
+  args."idea.properties" = ["idea.no.jre.check": true, "ide.mac.useNativeClipboard": "false"];
+  layoutMacApp(target, ch, args)
+}
diff --git a/python/edu/build/pycharm_edu_launcher.properties b/python/edu/build/pycharm_edu_launcher.properties
new file mode 100644
index 0000000..d355468
--- /dev/null
+++ b/python/edu/build/pycharm_edu_launcher.properties
@@ -0,0 +1,3 @@
+IDS_JDK_ENV_VAR=PYCHARM_JDK
+IDS_JDK_ONLY=false
+IDS_VM_OPTIONS=-Didea.platform.prefix=PyCharmEdu -Didea.no.jre.check=true -Didea.paths.selector=__PRODUCT_PATHS_SELECTOR__
diff --git a/python/edu/build/python-edu-build.iml b/python/edu/build/python-edu-build.iml
new file mode 100644
index 0000000..bda1778
--- /dev/null
+++ b/python/edu/build/python-edu-build.iml
@@ -0,0 +1,14 @@
+<?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$" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Groovy" level="project" />
+    <orderEntry type="module" module-name="jps-standalone-builder" />
+  </component>
+</module>
+
diff --git a/python/edu/build/resources/PC_instCom.ico b/python/edu/build/resources/PC_instCom.ico
new file mode 100644
index 0000000..44ecec9
--- /dev/null
+++ b/python/edu/build/resources/PC_instCom.ico
Binary files differ
diff --git a/python/edu/build/resources/PC_uninstCom.ico b/python/edu/build/resources/PC_uninstCom.ico
new file mode 100644
index 0000000..13e9f96
--- /dev/null
+++ b/python/edu/build/resources/PC_uninstCom.ico
Binary files differ
diff --git a/python/edu/build/resources/headerlogo.bmp b/python/edu/build/resources/headerlogo.bmp
new file mode 100644
index 0000000..c060992
--- /dev/null
+++ b/python/edu/build/resources/headerlogo.bmp
Binary files differ
diff --git a/python/edu/build/resources/logo.bmp b/python/edu/build/resources/logo.bmp
new file mode 100644
index 0000000..0f0f82d
--- /dev/null
+++ b/python/edu/build/resources/logo.bmp
Binary files differ
diff --git a/python/edu/build/resources/pycharm_inst.ico b/python/edu/build/resources/pycharm_inst.ico
new file mode 100644
index 0000000..6c75bcd
--- /dev/null
+++ b/python/edu/build/resources/pycharm_inst.ico
Binary files differ
diff --git a/python/edu/build/resources/pycharm_uninst.ico b/python/edu/build/resources/pycharm_uninst.ico
new file mode 100644
index 0000000..a5b9ad8
--- /dev/null
+++ b/python/edu/build/resources/pycharm_uninst.ico
Binary files differ
diff --git a/python/edu/build/strings.nsi b/python/edu/build/strings.nsi
new file mode 100644
index 0000000..fa10d42
--- /dev/null
+++ b/python/edu/build/strings.nsi
@@ -0,0 +1,13 @@
+!define MANUFACTURER "JetBrains"
+!define MUI_PRODUCT  "PyCharm Educational Edition"
+!define PRODUCT_FULL_NAME "JetBrains PyCharm Educational Edition"
+!define PRODUCT_EXE_FILE "pycharm.exe"
+!define PRODUCT_ICON_FILE "PC_instCom.ico"
+!define PRODUCT_UNINST_ICON_FILE "PC_uninstCom.ico"
+!define PRODUCT_LOGO_FILE "logo.bmp"
+!define PRODUCT_HEADER_FILE "headerlogo.bmp"
+
+; if SHOULD_SET_DEFAULT_INSTDIR != 0 then default installation directory will be directory where highest-numbered PyCharm build has been installed
+; set to 1 for release build
+!define SHOULD_SET_DEFAULT_INSTDIR "0"
+
diff --git a/python/edu/main_pycharm_edu.iml b/python/edu/main_pycharm_edu.iml
new file mode 100644
index 0000000..2acdd87
--- /dev/null
+++ b/python/edu/main_pycharm_edu.iml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="bootstrap" />
+    <orderEntry type="module" module-name="colorSchemes" />
+    <orderEntry type="module" module-name="svn4idea" />
+    <orderEntry type="module" module-name="git4idea" />
+    <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" />
+  </component>
+</module>
+
diff --git a/python/edu/python-educational.iml b/python/edu/python-educational.iml
new file mode 100644
index 0000000..92439c4
--- /dev/null
+++ b/python/edu/python-educational.iml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="python-openapi" exported="" />
+    <orderEntry type="module" module-name="platform-impl" />
+    <orderEntry type="module" module-name="lang-impl" />
+    <orderEntry type="library" name="Guava" level="project" />
+    <orderEntry type="module" module-name="python-pydev" />
+    <orderEntry type="library" exported="" name="XmlRPC" level="project" />
+    <orderEntry type="module" module-name="xdebugger-api" />
+    <orderEntry type="library" name="http-client-3.1" level="project" />
+    <orderEntry type="module" module-name="RegExpSupport" exported="" />
+    <orderEntry type="module" module-name="testRunner" />
+    <orderEntry type="module" module-name="smRunner" />
+    <orderEntry type="module" module-name="spellchecker" />
+    <orderEntry type="module" module-name="xdebugger-impl" />
+    <orderEntry type="module" module-name="xml-psi-impl" />
+    <orderEntry type="module" module-name="python-community" />
+  </component>
+</module>
+
diff --git a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
new file mode 100644
index 0000000..46695ef
--- /dev/null
+++ b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
@@ -0,0 +1,23 @@
+<component>
+  <company name="JetBrains s.r.o." url="http://www.jetbrains.com/?fromIDE"/>
+  <version major="3" 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"/>
+  <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"/>
+  <install-over minbuild="0" maxbuild="0" version="1.x"/>
+
+  <welcome-screen logo-url="/PyCharmCoreWelcomeScreen.png" caption-url="PyCharmCoreWelcomeCaption.png" slogan-url="/developSlogan.png"/>
+
+  <third-party url="http://confluence.jetbrains.com/display/PYH/Third-Party+Software+Used+by+PyCharm"/>
+
+  <update-urls logo-url="/Logo_welcomeScreen.png"
+               check="http://www.jetbrains.com/updates/updates.xml"
+               patches="http://download.jetbrains.com/python/"/>
+
+  <feedback eap-url="http://www.jetbrains.com/feedback/feedback.jsp?product=PyCharm&amp;build=$BUILD&amp;timezone=$TIMEZONE&amp;eval=$EVAL"
+            release-url="http://www.jetbrains.com/feedback/feedback.jsp?product=PyCharm&amp;build=$BUILD&amp;timezone=$TIMEZONE&amp;eval=$EVAL"/>
+  <help file="pycharmhelp.jar" root="pycharm"/>
+</component>
diff --git a/python/edu/src/META-INF/PyCharmEduPlugin.xml b/python/edu/src/META-INF/PyCharmEduPlugin.xml
new file mode 100644
index 0000000..9adf03d
--- /dev/null
+++ b/python/edu/src/META-INF/PyCharmEduPlugin.xml
@@ -0,0 +1,36 @@
+<idea-plugin version="2" xmlns:xi="http://www.w3.org/2001/XInclude">
+  <!-- Components and extensions declared in this file work ONLY in Pycharm Educational Edition -->
+
+  <application-components>
+    <component>
+      <implementation-class>com.jetbrains.python.edu.PyCharmEduInitialConfigurator$First</implementation-class>
+      <headless-implementation-class/>
+    </component>
+  </application-components>
+
+  <xi:include href="/META-INF/pycharm-core.xml" xpointer="xpointer(/idea-plugin/*)"/>
+
+  <xi:include href="/META-INF/python-core.xml" xpointer="xpointer(/idea-plugin/*)"/>
+
+
+  <actions>
+    <group overrides="true" class="com.intellij.openapi.actionSystem.EmptyActionGroup" id="ToolsMenu"/>
+
+    <group overrides="true" class="com.intellij.openapi.actionSystem.EmptyActionGroup" id="PrintExportGroup"/>
+    <group overrides="true" id="FileMainSettingsGroup">
+      <reference id="ShowSettings"/>
+      <separator/>
+      <reference id="ExportImportGroup"/>
+    </group>
+
+    <group overrides="true" class="com.intellij.openapi.actionSystem.EmptyActionGroup" id="EditBookmarksGroup"/>
+    <group overrides="true" class="com.intellij.openapi.actionSystem.EmptyActionGroup" id="AddToFavorites"/>
+    <group overrides="true" class="com.intellij.openapi.actionSystem.EmptyActionGroup" id="AddAllToFavorites"/>
+    <action overrides="true" class="com.intellij.openapi.actionSystem.EmptyAction" id="AddToFavoritesPopup"/>
+    <action overrides="true" class="com.intellij.openapi.actionSystem.EmptyAction" id="RemoveFromFavorites"/>
+
+    <action overrides="true" class="com.intellij.openapi.actionSystem.EmptyAction" id="NewHtmlFile"/>
+
+
+  </actions>
+</idea-plugin>
diff --git a/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java b/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
new file mode 100644
index 0000000..451c519
--- /dev/null
+++ b/python/edu/src/com/jetbrains/python/edu/PyCharmEduInitialConfigurator.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2000-2013 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.edu;
+
+import com.intellij.application.options.InitialConfigurationDialog;
+import com.intellij.codeInsight.CodeInsightSettings;
+import com.intellij.codeInsight.intention.IntentionActionBean;
+import com.intellij.codeInsight.intention.IntentionManager;
+import com.intellij.ide.AppLifecycleListener;
+import com.intellij.ide.GeneralSettings;
+import com.intellij.ide.RecentProjectsManagerBase;
+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;
+import com.intellij.openapi.fileChooser.impl.FileChooserUtil;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.keymap.Keymap;
+import com.intellij.openapi.keymap.ex.KeymapManagerEx;
+import com.intellij.openapi.keymap.impl.KeymapImpl;
+import com.intellij.openapi.project.Project;
+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;
+import com.intellij.openapi.wm.WindowManager;
+import com.intellij.platform.DirectoryProjectConfigurator;
+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;
+import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.Set;
+
+/**
+ * @author traff
+ */
+@SuppressWarnings({"UtilityClassWithoutPrivateConstructor", "UtilityClassWithPublicConstructor"})
+public class PyCharmEduInitialConfigurator {
+  @NonNls private static final String DISPLAYED_PROPERTY = "PyCharm.initialConfigurationShown";
+
+  @NonNls private static final String CONFIGURED = "PyCharm.InitialConfiguration";
+
+
+  public static class First {
+
+    public First() {
+      patchRootAreaExtensions();
+    }
+  }
+
+  /**
+   * @noinspection UnusedParameters
+   */
+  public PyCharmEduInitialConfigurator(MessageBus bus,
+                                       UISettings uiSettings,
+                                       CodeInsightSettings codeInsightSettings,
+                                       final PropertiesComponent propertiesComponent,
+                                       FileTypeManager fileTypeManager,
+                                       final ProjectManagerEx projectManager,
+                                       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;
+      codeInsightSettings.REFORMAT_ON_PASTE = CodeInsightSettings.NO_REFORMAT;
+
+      EditorSettingsExternalizable.getInstance().setVirtualSpace(false);
+      final CodeStyleSettings settings = CodeStyleSettingsManager.getInstance().getCurrentSettings();
+      settings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true;
+      settings.getCommonSettings(PythonLanguage.getInstance()).ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true;
+      UISettings.getInstance().SHOW_DIRECTORY_FOR_NON_UNIQUE_FILENAMES = true;
+      UISettings.getInstance().SHOW_MEMORY_INDICATOR = false;
+      final String ignoredFilesList = fileTypeManager.getIgnoredFilesList();
+      ApplicationManager.getApplication().invokeLater(new Runnable() {
+        @Override
+        public void run() {
+          ApplicationManager.getApplication().runWriteAction(new Runnable() {
+            @Override
+            public void run() {
+              FileTypeManager.getInstance().setIgnoredFilesList(ignoredFilesList + ";*$py.class");
+            }
+          });
+        }
+      });
+      PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP = false;
+    }
+    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();
+      }
+    });
+    bus.connect().subscribe(ProjectManager.TOPIC, new ProjectManagerAdapter() {
+      @Override
+      public void projectOpened(final Project project) {
+        if (project.isDefault()) return;
+        if (FileChooserUtil.getLastOpenedFile(project) == null) {
+          FileChooserUtil.setLastOpenedFile(project, VfsUtil.getUserHomeDir());
+        }
+
+        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)) {
+        rootArea.getExtensionPoint(ToolWindowEP.EP_NAME).unregisterExtension(ep);
+      }
+    }
+
+    for (DirectoryProjectConfigurator ep : Extensions.getExtensions(DirectoryProjectConfigurator.EP_NAME)) {
+      if (ep instanceof PlatformProjectViewOpener) {
+        rootArea.getExtensionPoint(DirectoryProjectConfigurator.EP_NAME).unregisterExtension(ep);
+      }
+    }
+
+    for (IntentionActionBean ep : Extensions.getExtensions(IntentionManager.EP_INTENTION_ACTIONS)) {
+      if ("org.intellij.lang.regexp.intention.CheckRegExpIntentionAction".equals(ep.className)) {
+        rootArea.getExtensionPoint(IntentionManager.EP_INTENTION_ACTIONS).unregisterExtension(ep);
+      }
+    }
+  }
+
+  private static void patchProjectAreaExtensions(@NotNull final Project project) {
+    for (SelectInTarget target : Extensions.getExtensions(SelectInTarget.EP_NAME, project)) {
+      if (ToolWindowId.FAVORITES_VIEW.equals(target.getToolWindowId())) {
+        Extensions.getArea(project).getExtensionPoint(SelectInTarget.EP_NAME).unregisterExtension(target);
+      }
+    }
+  }
+
+  private static void patchKeymap() {
+    Set<String> droppedActions = ContainerUtil.newHashSet(
+      "AddToFavoritesPopup", "RemoveFromFavorites",
+      "DatabaseView.ImportDataSources",
+      "CompileDirty", "Compile",
+      // hidden
+      "AddNewFavoritesList", "EditFavorites", "RemoveFromFavorites", "RenameFavoritesList", "RemoveFavoritesList");
+    KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
+
+
+    for (Keymap keymap : keymapManager.getAllKeymaps()) {
+      if (keymap.canModify()) continue;
+
+      KeymapImpl keymapImpl = (KeymapImpl)keymap;
+
+      for (String id : keymapImpl.getOwnActionIds()) {
+        if (droppedActions.contains(id)) keymapImpl.clearOwnActionsId(id);
+      }
+    }
+  }
+
+  private static void showInitialConfigurationDialog() {
+    final JFrame frame = WindowManager.getInstance().findVisibleFrame();
+    new InitialConfigurationDialog(frame, "Python").show();
+  }
+}
diff --git a/python/helpers/pycharm/_bdd_utils.py b/python/helpers/pycharm/_bdd_utils.py
new file mode 100644
index 0000000..0c92532
--- /dev/null
+++ b/python/helpers/pycharm/_bdd_utils.py
@@ -0,0 +1,201 @@
+# coding=utf-8
+"""
+Tools for running BDD frameworks in python.
+You probably need to extend BddRunner (see its doc).
+
+You may also need "get_path_by_args" that gets folder (current or passed as first argument)
+"""
+import os
+import time
+import abc
+
+import tcmessages
+
+
+__author__ = 'Ilya.Kazakevich'
+
+
+def get_path_by_args(arguments):
+    """
+    :type arguments list
+    :param arguments: arguments (sys.argv)
+    :return: tuple (base_dir, what_to_run) where dir is current or first argument from argv, checking it exists
+    :rtype tuple of str
+    """
+    what_to_run = arguments[1] if len(arguments) > 1 else "."
+    base_dir = what_to_run
+    assert os.path.exists(what_to_run), "{} does not exist".format(what_to_run)
+
+    if os.path.isfile(what_to_run):
+        base_dir = os.path.dirname(what_to_run) # User may point to the file directly
+    return base_dir, what_to_run
+
+
+class BddRunner(object):
+    """
+    Extends this class, implement abstract methods and use its API to implement new BDD frameworks.
+    Call "run()" to launch it.
+    This class does the following:
+    * Gets features to run (using "_get_features_to_run()") and calculates steps in it
+    * Reports steps to Intellij or TC
+    * Calls "_run_tests()" where *you* should install all hooks you need into your BDD and use "self._" functions
+    to report tests and features. It actually wraps tcmessages but adds some stuff like duration count etc
+    :param base_dir:
+    """
+    __metaclass__ = abc.ABCMeta
+
+    def __init__(self, base_dir):
+        """
+        :type base_dir str
+        :param base_dir base directory of your project
+        """
+        super(BddRunner, self).__init__()
+        self.tc_messages = tcmessages.TeamcityServiceMessages()
+        """
+        tcmessages TeamCity/Intellij test API. See TeamcityServiceMessages
+        """
+        self.__base_dir = base_dir
+        self.__last_test_start_time = None  # TODO: Doc when use
+        self.__last_test_name = None
+
+    def run(self):
+        """"
+        Runs runner. To be called right after constructor.
+        """
+        self.tc_messages.testCount(self._get_number_of_tests())
+        self.tc_messages.testMatrixEntered()
+        self._run_tests()
+
+    def __gen_location(self, location):
+        """
+        Generates location in format, supported by tcmessages
+        :param location object with "file" (relative to base_dir) and "line" fields.
+        :return: location in format file:line (as supported in tcmessages)
+        """
+        my_file = str(location.file).lstrip("/\\")
+        return "file:///{}:{}".format(os.path.normpath(os.path.join(self.__base_dir, my_file)), location.line)
+
+    def _test_undefined(self, test_name, location):
+        """
+        Mark test as undefined
+        :param test_name: name of test
+        :type test_name str
+        :param location its location
+
+        """
+        if test_name != self.__last_test_name:
+            self._test_started(test_name, location)
+        self._test_failed(test_name, message="Test undefined", details="Please define test")
+
+    def _test_skipped(self, test_name, reason, location):
+        """
+        Mark test as skipped
+        :param test_name: name of test
+        :param reason: why test was skipped
+        :type reason str
+        :type test_name str
+        :param location its location
+
+        """
+        if test_name != self.__last_test_name:
+            self._test_started(test_name, location)
+        self.tc_messages.testIgnored(test_name, "Skipped: {}".format(reason))
+        self.__last_test_name = None
+        pass
+
+    def _test_failed(self, name, message, details):
+        """
+        Report test failure
+        :param name: test name
+        :type name str
+        :param message: failure message
+        :type message str
+        :param details: failure details (probably stacktrace)
+        :type details str
+        """
+        self.tc_messages.testFailed(name, message=message, details=details)
+        self.__last_test_name = None
+
+    def _test_passed(self, name, duration=None):
+        """
+        Reports test passed
+        :param name: test name
+        :type name str
+        :param duration: time (in seconds) test took. Pass None if you do not know (we'll try to calculate it)
+        :type duration int
+        :return:
+        """
+        duration_to_report = duration
+        if self.__last_test_start_time and not duration:  # And not provided
+            duration_to_report = int(time.time() - self.__last_test_start_time)
+        self.tc_messages.testFinished(name, duration=int(duration_to_report))
+        self.__last_test_start_time = None
+        self.__last_test_name = None
+
+    def _test_started(self, name, location):
+        """
+        Reports test launched
+        :param name: test name
+        :param location object with "file" (relative to base_dir) and "line" fields.
+        :type name str
+        """
+        self.__last_test_start_time = time.time()
+        self.__last_test_name = name
+        self.tc_messages.testStarted(name, self.__gen_location(location))
+
+    def _feature_or_scenario(self, is_started, name, location):
+        """
+        Reports feature or scenario launched or stopped
+        :param is_started: started or finished?
+        :type is_started bool
+        :param name: scenario or feature name
+        :param location object with "file" (relative to base_dir) and "line" fields.
+        """
+        if is_started:
+            self.tc_messages.testSuiteStarted(name, self.__gen_location(location))
+        else:
+            self.tc_messages.testSuiteFinished(name)
+
+    def _background(self, is_started, location):
+        """
+        Reports background or stopped
+        :param is_started: started or finished?
+        :type is_started bool
+        :param location object with "file" (relative to base_dir) and "line" fields.
+        """
+        self._feature_or_scenario(is_started, "Background", location)
+
+    def _get_number_of_tests(self):
+        """"
+        Gets number of tests using "_get_features_to_run()" to obtain number of features to calculate.
+        Supports backgrounds as well.
+         :return number of steps
+         :rtype int
+        """
+        num_of_steps = 0
+        for feature in self._get_features_to_run():
+            if feature.background:
+                num_of_steps += len(feature.background.steps) * len(feature.scenarios)
+            for scenario in feature.scenarios:
+                num_of_steps += len(scenario.steps)
+        return num_of_steps
+
+    @abc.abstractmethod
+    def _get_features_to_run(self):
+        """
+        Implement it! Return list of features to run. Each "feature" should have "scenarios".
+         Each "scenario" should have "steps". Each "feature" may have "background" and each "background" should have
+          "steps". Duck typing.
+        :rtype list
+        :returns list of features
+        """
+        return []
+
+    @abc.abstractmethod
+    def _run_tests(self):
+        """
+        Implement it! It should launch tests using your BDD. Use "self._" functions to report results.
+        """
+        pass
+
+
diff --git a/python/helpers/pycharm/behave_runner.py b/python/helpers/pycharm/behave_runner.py
new file mode 100644
index 0000000..4a1b2f6
--- /dev/null
+++ b/python/helpers/pycharm/behave_runner.py
@@ -0,0 +1,242 @@
+# coding=utf-8
+"""
+Behave BDD runner.
+*FIRST* param now: folder to search "features" for.
+Each "features" folder should have features and "steps" subdir.
+
+Other args are tag expressionsin format (--tags=.. --tags=..).
+See https://pythonhosted.org/behave/behave.html#tag-expression
+"""
+import functools
+import sys
+import os
+import traceback
+
+from behave.formatter.base import Formatter
+from behave.model import Step, ScenarioOutline, Feature, Scenario
+from behave.tag_expression import TagExpression
+
+import _bdd_utils
+
+
+_MAX_STEPS_SEARCH_FEATURES = 5000  # Do not look for features in folder that has more that this number of children
+_FEATURES_FOLDER = 'features'  # "features" folder name.
+
+__author__ = 'Ilya.Kazakevich'
+
+from behave import configuration, runner
+from behave.formatter import formatters
+
+
+def _get_dirs_to_run(base_dir_to_search):
+    """
+    Searches for "features" dirs in some base_dir
+    :return: list of feature dirs to run
+    :rtype: list
+    :param base_dir_to_search root directory to search (should not have too many children!)
+    :type base_dir_to_search str
+
+    """
+    result = set()
+    for (step, (folder, sub_folders, files)) in enumerate(os.walk(base_dir_to_search)):
+        if os.path.basename(folder) == _FEATURES_FOLDER and os.path.isdir(folder):
+            result.add(os.path.abspath(folder))
+        if step == _MAX_STEPS_SEARCH_FEATURES:  # Guard
+            err = "Folder {} is too deep to find any features folder. Please provider concrete folder".format(
+                base_dir_to_search)
+            raise Exception(err)
+    return list(result)
+
+
+def _merge_hooks_wrapper(*hooks):
+    """
+    Creates wrapper that runs provided behave hooks sequentally
+    :param hooks: hooks to run
+    :return: wrapper
+    """
+    # TODO: Wheel reinvented!!!!
+    def wrapper(*args, **kwargs):
+        for hook in hooks:
+            hook(*args, **kwargs)
+
+    return wrapper
+
+
+class _RunnerWrapper(runner.Runner):
+    """
+    Wrapper around behave native wrapper. Has nothing todo with BddRunner!
+    We need it to support dry runs (to fetch data from scenarios) and hooks api
+    """
+
+    def __init__(self, config, hooks):
+        """
+        :type config configuration.Configuration
+        :param config behave configuration
+        :type hooks dict
+        :param hooks hooks in format "before_scenario" => f(context, scenario) to load after/before hooks, provided by user
+        """
+        super(_RunnerWrapper, self).__init__(config)
+        self.dry_run = False
+        """
+        Does not run tests (only fetches "self.features") if true. Runs tests otherwise.
+        """
+        self.__hooks = hooks
+
+    def load_hooks(self, filename='environment.py'):
+        """
+        Overrides parent "load_hooks" to add "self.__hooks"
+        :param filename: env. file name
+        """
+        super(_RunnerWrapper, self).load_hooks(filename)
+        for (hook_name, hook) in self.__hooks.items():
+            hook_to_add = hook
+            if hook_name in self.hooks:
+                user_hook = self.hooks[hook_name]
+                if hook_name.startswith("before"):
+                    user_and_custom_hook = [user_hook, hook]
+                else:
+                    user_and_custom_hook = [hook, user_hook]
+                hook_to_add = _merge_hooks_wrapper(*user_and_custom_hook)
+            self.hooks[hook_name] = hook_to_add
+
+    def run_model(self, features=None):
+        """
+        Overrides parent method to stop (do nothing) in case of "dry_run"
+        :param features: features to run
+        :return:
+        """
+        if self.dry_run:  # To stop further execution
+            return
+        return super(_RunnerWrapper, self).run_model(features)
+
+    def clean(self):
+        """
+        Cleans runner after dry run (clears hooks, features etc). To be called before real run!
+        """
+        self.dry_run = False
+        self.hooks.clear()
+        self.features = []
+
+
+class _BehaveRunner(_bdd_utils.BddRunner):
+    """
+    BddRunner for behave
+    """
+
+
+    def __process_hook(self, is_started, context, element):
+        """
+        Hook to be installed. Reports steps, features etc.
+        :param is_started true if test/feature/scenario is started
+        :type is_started bool
+        :param context behave context
+        :type context behave.runner.Context
+        :param element feature/suite/step
+        """
+        element.location.file = element.location.filename  # To preserve _bdd_utils contract
+        if isinstance(element, Step):
+            # Process step
+            if is_started:
+                self._test_started(element.name, element.location)
+            elif element.status == 'passed':
+                self._test_passed(element.name, element.duration)
+            elif element.status == 'failed':
+                try:
+                    trace = traceback.format_exc()
+                except Exception:
+                    trace = "".join(traceback.format_tb(element.exc_traceback))
+                self._test_failed(element.name, element.error_message, trace)
+            elif element.status == 'undefined':
+                self._test_undefined(element.name, element.location)
+            else:
+                self._test_skipped(element.name, element.status, element.location)
+        elif not is_started and isinstance(element, Scenario) and element.status == 'failed':
+            # To process scenarios with undefined/skipped tests
+            for step in element.steps:
+                assert isinstance(step, Step), step
+                if step.status not in ['passed', 'failed']:  # Something strange, probably skipped or undefined
+                    self.__process_hook(False, context, step)
+            self._feature_or_scenario(is_started, element.name, element.location)
+        elif isinstance(element, ScenarioOutline):
+            self._feature_or_scenario(is_started, str(element.examples), element.location)
+        else:
+            self._feature_or_scenario(is_started, element.name, element.location)
+
+    def __init__(self, config, base_dir):
+        """
+        :type config configuration.Configuration
+        """
+        super(_BehaveRunner, self).__init__(base_dir)
+        self.__config = config
+        # Install hooks
+        self.__real_runner = _RunnerWrapper(config, {
+            "before_feature": functools.partial(self.__process_hook, True),
+            "after_feature": functools.partial(self.__process_hook, False),
+            "before_scenario": functools.partial(self.__process_hook, True),
+            "after_scenario": functools.partial(self.__process_hook, False),
+            "before_step": functools.partial(self.__process_hook, True),
+            "after_step": functools.partial(self.__process_hook, False)
+        })
+
+    def _run_tests(self):
+        self.__real_runner.run()
+
+
+    def __filter_scenarios_by_tag(self, scenario):
+        """
+        Filters out scenarios that should be skipped by tags
+        :param scenario scenario to check
+        :return true if should pass
+        """
+        assert isinstance(scenario, Scenario), scenario
+        expected_tags = self.__config.tags
+        if not expected_tags:
+            return True  # No tags are required
+        return isinstance(expected_tags, TagExpression) and expected_tags.check(scenario.tags)
+
+
+    def _get_features_to_run(self):
+        self.__real_runner.dry_run = True
+        self.__real_runner.run()
+        features_to_run = self.__real_runner.features
+        self.__real_runner.clean()  # To make sure nothing left after dry run
+
+        # Change outline scenario skeletons with real scenarios
+        for feature in features_to_run:
+            assert isinstance(feature, Feature), feature
+            scenarios = []
+            for scenario in feature.scenarios:
+                if isinstance(scenario, ScenarioOutline):
+                    scenarios.extend(scenario.scenarios)
+                else:
+                    scenarios.append(scenario)
+            feature.scenarios = filter(self.__filter_scenarios_by_tag, scenarios)
+
+        return features_to_run
+
+
+if __name__ == "__main__":
+    # TODO: support all other params instead
+
+    class _Null(Formatter):
+        """
+        Null formater to prevent stdout output
+        """
+        pass
+
+    command_args = list(filter(None, sys.argv[1:]))
+    my_config = configuration.Configuration(command_args=command_args)
+    formatters.register_as(_Null, "com.intellij.python.null")
+    my_config.format = ["com.intellij.python.null"]  # To prevent output to stdout
+    my_config.reporters = []  # To prevent summary to stdout
+    my_config.stdout_capture = False  # For test output
+    my_config.stderr_capture = False  # For test output
+    (base_dir, what_to_run) = _bdd_utils.get_path_by_args(sys.argv)
+    if not my_config.paths:  # No path provided, trying to load dit manually
+        if os.path.isfile(what_to_run):  # File is provided, load it
+            my_config.paths = [what_to_run]
+        else:  # Dir is provided, find subdirs ro run
+            my_config.paths = _get_dirs_to_run(base_dir)
+    _BehaveRunner(my_config, base_dir).run()
+
+
diff --git a/python/helpers/pycharm/lettuce_runner.py b/python/helpers/pycharm/lettuce_runner.py
index 6aaa566..3cd1125 100644
--- a/python/helpers/pycharm/lettuce_runner.py
+++ b/python/helpers/pycharm/lettuce_runner.py
@@ -1,132 +1,112 @@
 # coding=utf-8
 """
 BDD lettuce framework runner
+TODO: Support other params (like tags) as well.
+Supports only 1 param now: folder to search "features" for.
 """
+import _bdd_utils
+
 __author__ = 'Ilya.Kazakevich'
-import os
 from lettuce.exceptions import ReasonToFail
-import time
 import sys
-import tcmessages
 import lettuce
 from lettuce import core
 
 
-# Error message about unsupported outlines
-_NO_OUTLINE_ERROR = "Outline scenarios are not supported due to https://github.com/gabrielfalcao/lettuce/issues/451"
-
-
-class LettuceRunner(object):
+class _LettuceRunner(_bdd_utils.BddRunner):
     """
-    TODO: Runs lettuce
+    Lettuce runner (BddRunner for lettuce)
     """
 
-    def __init__(self, base_dir):
+    def __init__(self, base_dir, what_to_run):
         """
+
         :param base_dir base directory to run tests in
         :type base_dir: str
+        :param what_to_run folder or file to run
+        :type what_to_run str
+        """
+        super(_LettuceRunner, self).__init__(base_dir)
+        self.__runner = lettuce.Runner(what_to_run)
 
-        """
-        self.base_dir = base_dir
-        self.runner = lettuce.Runner(base_dir)
-        self.messages = tcmessages.TeamcityServiceMessages()
-        self.test_start_time = None
+    def _get_features_to_run(self):
+        super(_LettuceRunner, self)._get_features_to_run()
+        if self.__runner.single_feature:  # We need to run one and only one feature
+            return [core.Feature.from_file(self.__runner.single_feature)]
 
-    def report_tests(self):
-        """
-        :returns : number of tests
-        :rtype : int
-        """
-        result = 0
-        for feature_file in self.runner.loader.find_feature_files():
+        # Find all features in dir
+        features = []
+        for feature_file in self.__runner.loader.find_feature_files():
             feature = core.Feature.from_file(feature_file)
-            for scenario in feature.scenarios:
-                assert isinstance(scenario, core.Scenario), scenario
-                if not scenario.outlines:
-                    result += len(scenario.steps)
-        self.messages.testCount(result)
+            assert isinstance(feature, core.Feature), feature
+            # TODO: cut out due to https://github.com/gabrielfalcao/lettuce/issues/451  Fix when this issue fixed
+            feature.scenarios = filter(lambda s: not s.outlines, feature.scenarios)
+            if feature.scenarios:
+                features.append(feature)
+        return features
 
-    def report_scenario_started(self, scenario):
+    def _run_tests(self):
+        super(_LettuceRunner, self)._run_tests()
+        self.__install_hooks()
+        self.__runner.run()
+
+    def __step(self, is_started, step):
+        """
+        Reports step start / stop
+        :type step core.Step
+        :param step: step
+        """
+        test_name = step.sentence
+        if is_started:
+            self._test_started(test_name, step.described_at)
+        elif step.passed:
+            self._test_passed(test_name)
+        elif step.failed:
+            reason = step.why
+            assert isinstance(reason, ReasonToFail), reason
+            self._test_failed(test_name, message=reason.exception, details=reason.traceback)
+        elif step.has_definition:
+            self._test_skipped(test_name, "In lettuce, we do know the reason", step.described_at)
+        else:
+            self._test_undefined(test_name, step.described_at)
+
+    def __install_hooks(self):
+        """
+        Installs required hooks
+        """
+
+        # Install hooks
+        lettuce.before.each_feature(
+            lambda f: self._feature_or_scenario(True, f.name, f.described_at))
+        lettuce.after.each_feature(
+            lambda f: self._feature_or_scenario(False, f.name, f.described_at))
+
+        lettuce.before.each_scenario(
+            lambda s: self.__scenario(True, s))
+        lettuce.after.each_scenario(
+            lambda s: self.__scenario(False, s))
+
+        lettuce.before.each_background(
+            lambda b, *args: self._background(True, b.feature.described_at))
+        lettuce.after.each_background(
+            lambda b, *args: self._background(False, b.feature.described_at))
+
+        lettuce.before.each_step(lambda s: self.__step(True, s))
+        lettuce.after.each_step(lambda s: self.__step(False, s))
+
+    def __scenario(self, is_started, scenario):
         """
         Reports scenario launched
         :type scenario core.Scenario
         :param scenario: scenario
         """
         if scenario.outlines:
-            self.messages.testIgnored(scenario.name,
-                                      _NO_OUTLINE_ERROR)
             scenario.steps = []  # Clear to prevent running. TODO: Fix when this issue fixed
             scenario.background = None  # TODO: undocumented
             return
-        self.report_suite(True, scenario.name, scenario.described_at)
-
-    def report_suite(self, is_start, name, described_at):
-        """
-        Reports some suite (scenario, feature, background etc) is started or stopped
-        :param is_start: started or not
-        :param name: suite name
-        :param described_at: where it is described (file, line)
-        :return:
-        """
-        if is_start:
-            self.messages.testSuiteStarted(name, self._gen_location(described_at))
-        else:
-            self.messages.testSuiteFinished(name)
-
-    def report_step(self, is_start, step):
-        """
-        Reports step start / stop
-        :param is_start: true if step started
-        :type step core.Step
-        :param step: step
-        """
-        test_name = step.sentence
-        if is_start:
-            self.test_start_time = time.time()
-            self.messages.testStarted(test_name, self._gen_location(step.described_at))
-        elif step.passed:
-            duration = 0
-            if self.test_start_time:
-                duration = long(time.time() - self.test_start_time)
-            self.messages.testFinished(test_name, duration=duration)
-            self.test_start_time = None
-        elif step.failed:
-            reason = step.why
-            assert isinstance(reason, ReasonToFail), reason
-            self.messages.testFailed(test_name, message=reason.exception, details=reason.traceback)
-
-    def _gen_location(self, description):
-        """
-        :param description: "described_at" (file, line)
-        :return: location in format file:line by "described_at"
-        """
-        return "file:///{}/{}:{}".format(self.base_dir, description.file, description.line)
-
-    def run(self):
-        """
-        Launches runner
-        """
-        self.report_tests()
-        self.messages.testMatrixEntered()
-
-        lettuce.before.each_feature(lambda f: self.report_suite(True, f.name, f.described_at))
-        lettuce.after.each_feature(lambda f: self.report_suite(False, f.name, f.described_at))
-
-        lettuce.before.each_scenario(lambda s: self.report_scenario_started(s))
-        lettuce.after.each_scenario(lambda s: self.report_suite(False, s.name, s.described_at))
-
-        lettuce.before.each_background(
-            lambda b, *args: self.report_suite(True, "Scenario background", b.feature.described_at))
-        lettuce.after.each_background(
-            lambda b, *args: self.report_suite(False, "Scenario background", b.feature.described_at))
-
-        lettuce.before.each_step(lambda s: self.report_step(True, s))
-        lettuce.after.each_step(lambda s: self.report_step(False, s))
-
-        self.runner.run()
+        self._feature_or_scenario(is_started, scenario.name, scenario.described_at)
 
 
 if __name__ == "__main__":
-    path = sys.argv[1] if len(sys.argv) > 1 else "."
-    assert os.path.exists(path), "{} does not exist".format(path)
-    LettuceRunner(path).run()
\ No newline at end of file
+    (base_dir, what_to_run) = _bdd_utils.get_path_by_args(sys.argv)
+    _LettuceRunner(base_dir, what_to_run).run()
\ No newline at end of file
diff --git a/python/helpers/pycharm/tcunittest.py b/python/helpers/pycharm/tcunittest.py
index b6950c9..99b3059 100644
--- a/python/helpers/pycharm/tcunittest.py
+++ b/python/helpers/pycharm/tcunittest.py
@@ -6,14 +6,16 @@
 
 PYTHON_VERSION_MAJOR = sys.version_info[0]
 
+
 def strclass(cls):
   if not cls.__name__:
     return cls.__module__
   return "%s.%s" % (cls.__module__, cls.__name__)
 
+
 def smart_str(s):
-  encoding='utf-8'
-  errors='strict'
+  encoding = 'utf-8'
+  errors = 'strict'
   if PYTHON_VERSION_MAJOR < 3:
     is_string = isinstance(s, basestring)
   else:
@@ -33,6 +35,7 @@
   else:
     return s
 
+
 class TeamcityTestResult(TestResult):
   def __init__(self, stream=sys.stdout, *args, **kwargs):
     TestResult.__init__(self)
@@ -41,42 +44,47 @@
     self.output = stream
     self.messages = TeamcityServiceMessages(self.output, prepend_linebreak=True)
     self.messages.testMatrixEntered()
+    self.current_failed = False
     self.current_suite = None
+    self.subtest_suite = None
 
   def find_first(self, val):
     quot = val[0]
     count = 1
     quote_ind = val[count:].find(quot)
-    while quote_ind != -1 and val[count+quote_ind-1] == "\\":
+    while quote_ind != -1 and val[count + quote_ind - 1] == "\\":
       count = count + quote_ind + 1
       quote_ind = val[count:].find(quot)
 
-    return val[0:quote_ind+count+1]
+    return val[0:quote_ind + count + 1]
 
   def find_second(self, val):
     val_index = val.find("!=")
     if val_index != -1:
       count = 1
-      val = val[val_index+2:].strip()
+      val = val[val_index + 2:].strip()
       quot = val[0]
       quote_ind = val[count:].find(quot)
-      while quote_ind != -1 and val[count+quote_ind-1] == "\\":
+      while quote_ind != -1 and val[count + quote_ind - 1] == "\\":
         count = count + quote_ind + 1
         quote_ind = val[count:].find(quot)
-      return val[0:quote_ind+count+1]
+      return val[0:quote_ind + count + 1]
 
     else:
       quot = val[-1]
-      quote_ind = val[:len(val)-1].rfind(quot)
-      while quote_ind != -1 and val[quote_ind-1] == "\\":
-        quote_ind = val[:quote_ind-1].rfind(quot)
+      quote_ind = val[:len(val) - 1].rfind(quot)
+      while quote_ind != -1 and val[quote_ind - 1] == "\\":
+        quote_ind = val[:quote_ind - 1].rfind(quot)
       return val[quote_ind:]
 
   def formatErr(self, err):
     exctype, value, tb = err
     return ''.join(traceback.format_exception(exctype, value, tb))
 
-  def getTestName(self, test):
+  def getTestName(self, test, is_subtest=False):
+    if is_subtest:
+      test_name = self.getTestName(test.test_case)
+      return "{} {}".format(test_name, test._subDescription())
     if hasattr(test, '_testMethodName'):
       if test._testMethodName == "runTest":
         return str(test)
@@ -95,10 +103,13 @@
     TestResult.addSuccess(self, test)
 
   def addError(self, test, err):
+    self.init_suite(test)
+    self.current_failed = True
     TestResult.addError(self, test, err)
 
     err = self._exc_info_to_string(err, test)
 
+    self.messages.testStarted(self.getTestName(test))
     self.messages.testError(self.getTestName(test),
                             message='Error', details=err)
 
@@ -108,6 +119,8 @@
     return error_value.split('assert')[-1].strip()
 
   def addFailure(self, test, err):
+    self.init_suite(test)
+    self.current_failed = True
     TestResult.addFailure(self, test, err)
 
     error_value = smart_str(err[1])
@@ -119,7 +132,7 @@
     self_find_second = self.find_second(error_value)
     quotes = ["'", '"']
     if (self_find_first[0] == self_find_first[-1] and self_find_first[0] in quotes and
-        self_find_second[0] == self_find_second[-1] and self_find_second[0] in quotes):
+            self_find_second[0] == self_find_second[-1] and self_find_second[0] in quotes):
       # let's unescape strings to show sexy multiline diff in PyCharm.
       # By default all caret return chars are escaped by testing framework
       first = self._unescape(self_find_first)
@@ -128,10 +141,13 @@
       first = second = ""
     err = self._exc_info_to_string(err, test)
 
+    self.messages.testStarted(self.getTestName(test))
     self.messages.testFailed(self.getTestName(test),
                              message='Failure', details=err, expected=first, actual=second)
 
   def addSkip(self, test, reason):
+    self.init_suite(test)
+    self.current_failed = True
     self.messages.testIgnored(self.getTestName(test), message=reason)
 
   def __getSuite(self, test):
@@ -149,10 +165,10 @@
       try:
         source_file = inspect.getsourcefile(test.__class__)
         if source_file:
-            source_dir_splitted = source_file.split("/")[:-1]
-            source_dir = "/".join(source_dir_splitted) + "/"
+          source_dir_splitted = source_file.split("/")[:-1]
+          source_dir = "/".join(source_dir_splitted) + "/"
         else:
-            source_dir = ""
+          source_dir = ""
       except TypeError:
         source_dir = ""
 
@@ -163,20 +179,52 @@
     return (suite, location, suite_location)
 
   def startTest(self, test):
+    self.current_failed = False
+    setattr(test, "startTime", datetime.datetime.now())
+
+  def init_suite(self, test):
     suite, location, suite_location = self.__getSuite(test)
     if suite != self.current_suite:
       if self.current_suite:
         self.messages.testSuiteFinished(self.current_suite)
       self.current_suite = suite
       self.messages.testSuiteStarted(self.current_suite, location=suite_location)
-    setattr(test, "startTime", datetime.datetime.now())
-    self.messages.testStarted(self.getTestName(test), location=location)
+    return location
 
   def stopTest(self, test):
     start = getattr(test, "startTime", datetime.datetime.now())
     d = datetime.datetime.now() - start
-    duration=d.microseconds / 1000 + d.seconds * 1000 + d.days * 86400000
-    self.messages.testFinished(self.getTestName(test), duration=int(duration))
+    duration = d.microseconds / 1000 + d.seconds * 1000 + d.days * 86400000
+    if not self.subtest_suite:
+      if not self.current_failed:
+        location = self.init_suite(test)
+        self.messages.testStarted(self.getTestName(test), location=location)
+        self.messages.testFinished(self.getTestName(test), duration=int(duration))
+    else:
+      self.messages.testSuiteFinished(self.subtest_suite)
+      self.subtest_suite = None
+
+
+  def addSubTest(self, test, subtest, err):
+    suite_name = self.getTestName(test)  # + " (subTests)"
+    if not self.subtest_suite:
+      self.subtest_suite = suite_name
+      self.messages.testSuiteStarted(self.subtest_suite)
+    else:
+      if suite_name != self.subtest_suite:
+        self.messages.testSuiteFinished(self.subtest_suite)
+        self.subtest_suite = suite_name
+        self.messages.testSuiteStarted(self.subtest_suite)
+
+    name = self.getTestName(subtest, True)
+    if err is not None:
+      error = self._exc_info_to_string(err, test)
+      self.messages.testStarted(name)
+      self.messages.testFailed(name, message='Failure', details=error)
+    else:
+      self.messages.testStarted(name)
+      self.messages.testFinished(name)
+
 
   def endLastSuite(self):
     if self.current_suite:
@@ -187,6 +235,7 @@
     # do not use text.decode('string_escape'), it leads to problems with different string encodings given
     return text.replace("\\n", "\n")
 
+
 class TeamcityTestRunner(object):
   def __init__(self, stream=sys.stdout):
     self.stream = stream
diff --git a/python/ide/src/com/jetbrains/python/newProject/PyCharmNewProjectDialog.java b/python/ide/src/com/jetbrains/python/newProject/PyCharmNewProjectDialog.java
index d186f33..7570e3f 100644
--- a/python/ide/src/com/jetbrains/python/newProject/PyCharmNewProjectDialog.java
+++ b/python/ide/src/com/jetbrains/python/newProject/PyCharmNewProjectDialog.java
@@ -16,9 +16,11 @@
 package com.jetbrains.python.newProject;
 
 import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.extensions.Extensions;
 import com.intellij.openapi.project.ProjectManager;
 import com.intellij.openapi.ui.DialogWrapper;
 import com.intellij.openapi.wm.impl.welcomeScreen.CardActionsPanel;
+import com.intellij.platform.DirectoryProjectGenerator;
 import com.jetbrains.python.newProject.actions.PyCharmNewProjectStep;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -42,7 +44,8 @@
         PyCharmNewProjectDialog.this.close(OK_EXIT_CODE);
       }
     };
-    final DefaultActionGroup root = new PyCharmNewProjectStep(runnable);
+    final DirectoryProjectGenerator[] generators = Extensions.getExtensions(DirectoryProjectGenerator.EP_NAME);
+    final DefaultActionGroup root = new PyCharmNewProjectStep(generators.length == 0 ? "Create Project" : "Select Project Type", runnable);
 
     return new CardActionsPanel(root) {
 
@@ -53,6 +56,7 @@
 
       @Override
       public Dimension getMinimumSize() {
+        if (generators.length == 0) return new Dimension(550, 200);
         return new Dimension(650, 450);
       }
     };
diff --git a/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java b/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java
index d9f3cf2..6ba283c 100644
--- a/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java
+++ b/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java
@@ -8,6 +8,7 @@
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.Presentation;
 import com.intellij.openapi.actionSystem.impl.ActionButtonWithText;
+import com.intellij.openapi.extensions.Extensions;
 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
 import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
 import com.intellij.openapi.project.DumbAware;
@@ -45,10 +46,12 @@
 
 import javax.swing.*;
 import javax.swing.border.Border;
+import javax.swing.border.LineBorder;
 import javax.swing.event.DocumentEvent;
 import java.awt.*;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
 import java.awt.event.MouseEvent;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
@@ -62,9 +65,10 @@
   private boolean myInstallFramework;
   private TextFieldWithBrowseButton myLocationField;
   protected final File myProjectDirectory;
-  private ActionButtonWithText myCreateButton;
+  private Button myCreateButton;
   private JLabel myErrorLabel;
   private AnAction myCreateAction;
+  private Sdk mySdk;
 
   public AbstractProjectSettingsStep(DirectoryProjectGenerator projectGenerator, NullableConsumer<AbstractProjectSettingsStep> callback) {
     super();
@@ -104,16 +108,24 @@
 
   @Override
   public JPanel createPanel() {
-    final JPanel mainPanel = new JPanel(new BorderLayout());
+    final JPanel basePanel = createBasePanel();
+    final JPanel mainPanel = new JPanel(new BorderLayout()) {
+      @Override
+      protected void paintComponent(Graphics g) {
+        myLocationField.requestFocus();
+      }
+    };
+
     final JPanel scrollPanel = new JPanel(new BorderLayout());
 
-    mainPanel.setPreferredSize(new Dimension(mainPanel.getPreferredSize().width, 400));
+    final DirectoryProjectGenerator[] generators = Extensions.getExtensions(DirectoryProjectGenerator.EP_NAME);
+    final int height = generators.length == 0 ? 150 : 400;
+    mainPanel.setPreferredSize(new Dimension(mainPanel.getPreferredSize().width, height));
     myErrorLabel = new JLabel("");
     myErrorLabel.setForeground(JBColor.RED);
     myCreateButton = new Button(myCreateAction, myCreateAction.getTemplatePresentation());
 
-    final JPanel panel = createBasePanel();
-    scrollPanel.add(panel, BorderLayout.NORTH);
+    scrollPanel.add(basePanel, BorderLayout.NORTH);
     final JPanel advancedSettings = createAdvancedSettings();
     if (advancedSettings != null) {
       scrollPanel.add(advancedSettings, BorderLayout.CENTER);
@@ -125,8 +137,6 @@
 
     final JPanel bottomPanel = new JPanel(new BorderLayout());
 
-
-    myCreateButton.setPreferredSize(new Dimension(mainPanel.getPreferredSize().width, 40));
     bottomPanel.add(myErrorLabel, BorderLayout.NORTH);
     bottomPanel.add(myCreateButton, BorderLayout.EAST);
     mainPanel.add(bottomPanel, BorderLayout.SOUTH);
@@ -150,7 +160,22 @@
     final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
     myLocationField.addBrowseFolderListener("Select base directory", "Select base directory for the Project",
                                             null, descriptor);
-
+    myLocationField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
+      @Override
+      protected void textChanged(DocumentEvent e) {
+        if (myProjectGenerator instanceof PythonProjectGenerator) {
+          String path = myLocationField.getText().trim();
+          if (path.endsWith(File.separator)) {
+            path = path.substring(0, path.length() - File.separator.length());
+          }
+          int ind = path.lastIndexOf(File.separator);
+          if (ind != -1) {
+            String projectName = path.substring(ind + 1, path.length());
+            ((PythonProjectGenerator)myProjectGenerator).locationChanged(projectName);
+          }
+        }
+      }
+    });
     final JLabel locationLabel = new JLabel("Location:");
     c.gridx = 0;
     c.gridy = 0;
@@ -193,13 +218,23 @@
     c.gridy = 1;
     c.weightx = 1.;
     panel.add(mySdkCombo, c);
-
+    final JPanel basePanelExtension = extendBasePanel();
+    if (basePanelExtension != null) {
+      c.gridwidth = 2;
+      c.gridy = 2;
+      c.gridx = 0;
+      panel.add(basePanelExtension, c);
+    }
     registerValidators();
     return panel;
   }
 
-  protected void registerValidators() {
+  @Nullable
+  protected JPanel extendBasePanel() {
+    return null;
+  }
 
+  protected void registerValidators() {
     myLocationField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
       @Override
       protected void textChanged(DocumentEvent e) {
@@ -358,7 +393,8 @@
 
     public Button(AnAction action, Presentation presentation) {
       super(action, presentation, "NewProject", new Dimension(70, 50));
-      myBorder = UIUtil.isUnderDarcula() ? UIUtil.getButtonBorder() : BorderFactory.createLineBorder(UIUtil.getBorderColor());
+      final Border border = new LineBorder(JBColor.border(), 1, true);
+      myBorder = UIUtil.isUnderDarcula() ? UIUtil.getButtonBorder() : border;
       setBorder(myBorder);
     }
 
@@ -368,6 +404,23 @@
     }
 
     @Override
+    public boolean isFocusable() {
+      return true;
+    }
+
+    @Override
+    protected void processFocusEvent(FocusEvent e) {
+      super.processFocusEvent(e);
+      if (e.getID() == FocusEvent.FOCUS_GAINED) {
+        processMouseEvent(new MouseEvent(this, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), 0, 0, 0, 0, false));
+
+      }
+      else if (e.getID() == FocusEvent.FOCUS_LOST) {
+        processMouseEvent(new MouseEvent(this, MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), 0, 0, 0, 0, false));
+      }
+    }
+
+    @Override
     public Insets getInsets() {
       return new Insets(5,10,5,5);
     }
@@ -377,11 +430,6 @@
       return SwingConstants.LEFT;
     }
 
-    @Override
-    public String getToolTipText() {
-      return null;
-    }
-
     protected void processMouseEvent(MouseEvent e) {
       super.processMouseEvent(e);
       if (e.getID() == MouseEvent.MOUSE_ENTERED) {
@@ -394,9 +442,14 @@
   }
 
   public Sdk getSdk() {
+    if (mySdk != null) return mySdk;
     return (Sdk)mySdkCombo.getComboBox().getSelectedItem();
   }
 
+  public void setSdk(final Sdk sdk) {
+    mySdk = sdk;
+  }
+
   public String getProjectLocation() {
     return myLocationField.getText();
   }
diff --git a/python/ide/src/com/jetbrains/python/newProject/actions/ProjectSpecificSettingsStep.java b/python/ide/src/com/jetbrains/python/newProject/actions/ProjectSpecificSettingsStep.java
index ebd0658..5e60601 100644
--- a/python/ide/src/com/jetbrains/python/newProject/actions/ProjectSpecificSettingsStep.java
+++ b/python/ide/src/com/jetbrains/python/newProject/actions/ProjectSpecificSettingsStep.java
@@ -15,6 +15,7 @@
  */
 package com.jetbrains.python.newProject.actions;
 
+import com.intellij.facet.ui.ValidationResult;
 import com.intellij.ide.util.projectWizard.WebProjectTemplate;
 import com.intellij.openapi.project.DumbAware;
 import com.intellij.openapi.ui.VerticalFlowLayout;
@@ -46,6 +47,12 @@
     if (advancedSettings != null) {
       final JPanel jPanel = new JPanel(new VerticalFlowLayout());
       final HideableDecorator deco = new HideableDecorator(jPanel, "Mor&e Settings", false);
+      boolean isValid = checkValid();
+      deco.setOn(!isValid);
+      if (myProjectGenerator instanceof PythonProjectGenerator && !deco.isExpanded()) {
+        final ValidationResult result = ((PythonProjectGenerator)myProjectGenerator).warningValidation(getSdk());
+        deco.setOn(!result.isOk());
+      }
       deco.setContentComponent(advancedSettings);
       return jPanel;
     }
diff --git a/python/ide/src/com/jetbrains/python/newProject/actions/PyCharmNewProjectStep.java b/python/ide/src/com/jetbrains/python/newProject/actions/PyCharmNewProjectStep.java
index b6134ed..4f9f107 100644
--- a/python/ide/src/com/jetbrains/python/newProject/actions/PyCharmNewProjectStep.java
+++ b/python/ide/src/com/jetbrains/python/newProject/actions/PyCharmNewProjectStep.java
@@ -62,8 +62,8 @@
 public class PyCharmNewProjectStep extends DefaultActionGroup implements DumbAware {
   private static final Logger LOG = Logger.getInstance(PyCharmNewProjectStep.class);
 
-  public PyCharmNewProjectStep(@Nullable final Runnable runnable) {
-    super("Select Project Type", true);
+  public PyCharmNewProjectStep(@NotNull final String name, @Nullable final Runnable runnable) {
+    super(name, true);
 
     final NullableConsumer<AbstractProjectSettingsStep> callback = new NullableConsumer<AbstractProjectSettingsStep>() {
       @Override
@@ -87,6 +87,7 @@
           sdk = SdkConfigurationUtil.setupSdk(ProjectJdkTable.getInstance().getAllJdks(), sdkHome, PythonSdkType.getInstance(), true, null,
                                               null);
           model.addSdk(sdk);
+          settingsStep.setSdk(sdk);
           try {
             model.apply();
           }
@@ -165,6 +166,9 @@
     add(action);
 
     final DirectoryProjectGenerator[] generators = Extensions.getExtensions(DirectoryProjectGenerator.EP_NAME);
+    if (generators.length == 0) {
+      action.setPopup(false);
+    }
     Arrays.sort(generators, new Comparator<DirectoryProjectGenerator>() {
       @Override
       public int compare(DirectoryProjectGenerator o1, DirectoryProjectGenerator o2) {
@@ -188,7 +192,7 @@
   }
 
   public PyCharmNewProjectStep() {
-    this(null);
+    this("Select Project Type", null);
 
   }
 
diff --git a/python/openapi/src/com/jetbrains/python/newProject/PythonProjectGenerator.java b/python/openapi/src/com/jetbrains/python/newProject/PythonProjectGenerator.java
index 5577959..1432e46 100644
--- a/python/openapi/src/com/jetbrains/python/newProject/PythonProjectGenerator.java
+++ b/python/openapi/src/com/jetbrains/python/newProject/PythonProjectGenerator.java
@@ -31,6 +31,8 @@
     myListeners.add(listener);
   }
 
+  public void locationChanged(@NotNull final String newLocation) {}
+
   public interface SettingsListener {
     void stateChanged();
   }
diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java b/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java
index 9f6e7c9..daa8b30 100644
--- a/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java
+++ b/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java
@@ -37,15 +37,17 @@
   public abstract PyStringLiteralExpression createStringLiteralAlreadyEscaped(String str);
 
 
-
-
   /**
    * Creates a string literal, adding appropriate quotes, properly escaping characters inside.
+   *
    * @param destination where the literal is destined to; used to determine the encoding.
    * @param unescaped   the string
-   * @return            a newly created literal
+   * @param preferUTF8 try to use UTF8 (would use ascii if false)
+   * @return a newly created literal
    */
-  public abstract PyStringLiteralExpression createStringLiteralFromString(@Nullable PsiFile destination, String unescaped);
+  public abstract PyStringLiteralExpression createStringLiteralFromString(@Nullable PsiFile destination, String unescaped,
+                                                                          boolean preferUTF8);
+
   public abstract PyStringLiteralExpression createStringLiteralFromString(@NotNull String unescaped);
 
   public abstract PyStringLiteralExpression createStringLiteral(@NotNull PyStringLiteralExpression oldElement, @NotNull String unescaped);
@@ -59,11 +61,11 @@
   public abstract PyBinaryExpression createBinaryExpression(String s, PyExpression expr, PyExpression listLiteral);
 
   /**
-   * @deprecated  use the overload with language level specified
    * @param text the text to create an expression from
    * @return the expression
+   * @deprecated use the overload with language level specified
    */
-  public abstract  PyExpression createExpressionFromText(String text);
+  public abstract PyExpression createExpressionFromText(String text);
 
   public abstract PyExpression createExpressionFromText(final LanguageLevel languageLevel, String text);
 
@@ -71,9 +73,9 @@
    * Adds elements to list inserting required commas.
    * Method is like {@link #insertItemIntoList(PyElement, PyExpression, PyExpression)} but does not add unneeded commas.
    *
-   * @param list where to add
+   * @param list      where to add
    * @param afterThis after which element it should be added (null for add to the head)
-   * @param toInsert what to insert
+   * @param toInsert  what to insert
    * @return newly inserted element
    */
   @NotNull
@@ -92,7 +94,10 @@
 
   public abstract PyImportElement createImportElement(final LanguageLevel languageLevel, String name);
 
-  public abstract PyFunction createProperty(final LanguageLevel languageLevel, String propertyName, String fieldName, AccessDirection accessDirection);
+  public abstract PyFunction createProperty(final LanguageLevel languageLevel,
+                                            String propertyName,
+                                            String fieldName,
+                                            AccessDirection accessDirection);
 
   @NotNull
   public abstract <T> T createFromText(LanguageLevel langLevel, Class<T> aClass, final String text);
@@ -103,6 +108,7 @@
   /**
    * Creates an arbitrary PSI element from text, by creating a bigger construction and then cutting the proper subelement.
    * Will produce all kinds of exceptions if the path or class would not match the PSI tree.
+   *
    * @param langLevel the language level to use for parsing the text
    * @param aClass    class of the PSI element; may be an interface not descending from PsiElement, as long as target node can be cast to it
    * @param text      text to parse
@@ -122,8 +128,15 @@
   public abstract PsiFile createDummyFile(LanguageLevel langLevel, String contents);
 
   public abstract PyExpressionStatement createDocstring(String content);
+
   public abstract PyPassStatement createPassStatement();
 
   @NotNull
   public abstract PyDecoratorList createDecoratorList(@NotNull final String... decoratorTexts);
+
+  /**
+   * Creates new line whitespace
+   */
+  @NotNull
+  public abstract PsiElement createNewLine();
 }
diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyElsePart.java b/python/psi-api/src/com/jetbrains/python/psi/PyElsePart.java
index c329162..41b715f 100644
--- a/python/psi-api/src/com/jetbrains/python/psi/PyElsePart.java
+++ b/python/psi-api/src/com/jetbrains/python/psi/PyElsePart.java
@@ -15,17 +15,10 @@
  */
 package com.jetbrains.python.psi;
 
-import org.jetbrains.annotations.Nullable;
-
 /**
  * The 'else:' part of various compound statements.
  * User: dcheryasov
  * Date: Mar 15, 2009 9:34:51 PM
  */
 public interface PyElsePart extends PyStatementPart {
-  /**
-   * @return the body of the 'else' part.
-   */
-  @Nullable
-  PyStatementList getStatementList();
 }
diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyStatementPart.java b/python/psi-api/src/com/jetbrains/python/psi/PyStatementPart.java
index f491a6a..a14a91e 100644
--- a/python/psi-api/src/com/jetbrains/python/psi/PyStatementPart.java
+++ b/python/psi-api/src/com/jetbrains/python/psi/PyStatementPart.java
@@ -15,19 +15,19 @@
  */
 package com.jetbrains.python.psi;
 
-import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Abstract part of a multipart statement.
  * User: dcheryasov
  * Date: Mar 16, 2009 4:34:59 AM
  */
-public interface PyStatementPart extends PyElement {
+public interface PyStatementPart extends PyElement, PyStatementListContainer {
   PyStatementPart[] EMPTY_ARRAY = new PyStatementPart[0];
 
   /**
    * @return the body of the part.
    */
-  @Nullable
+  @NotNull
   PyStatementList getStatementList();
 }
diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyWithStatement.java b/python/psi-api/src/com/jetbrains/python/psi/PyWithStatement.java
index 201ae30..b23ca63 100644
--- a/python/psi-api/src/com/jetbrains/python/psi/PyWithStatement.java
+++ b/python/psi-api/src/com/jetbrains/python/psi/PyWithStatement.java
@@ -18,6 +18,6 @@
 /**
  * @author yole
  */
-public interface PyWithStatement extends PyStatement, NameDefiner {
+public interface PyWithStatement extends PyStatement, NameDefiner, PyStatementListContainer {
   PyWithItem[] getWithItems();
 }
diff --git a/python/psi-api/src/com/jetbrains/python/psi/StructuredDocString.java b/python/psi-api/src/com/jetbrains/python/psi/StructuredDocString.java
index fa88810..808ec47 100644
--- a/python/psi-api/src/com/jetbrains/python/psi/StructuredDocString.java
+++ b/python/psi-api/src/com/jetbrains/python/psi/StructuredDocString.java
@@ -25,6 +25,15 @@
  * @author vlan
  */
 public interface StructuredDocString {
+  /**
+   * Creates parameter type documentation specific for certain doct type
+   * @param name param name
+   * @param type param type
+   * @return text to add to docsting
+   */
+  @NotNull
+  String createParameterType(@NotNull String name, @NotNull String type);
+
   String getDescription();
 
   String getSummary();
diff --git a/python/python-rest/src/com/jetbrains/rest/run/RestCommandLineState.java b/python/python-rest/src/com/jetbrains/rest/run/RestCommandLineState.java
index e421df8..e5b52b5 100644
--- a/python/python-rest/src/com/jetbrains/rest/run/RestCommandLineState.java
+++ b/python/python-rest/src/com/jetbrains/rest/run/RestCommandLineState.java
@@ -19,7 +19,6 @@
 import com.intellij.execution.configurations.GeneralCommandLine;
 import com.intellij.execution.configurations.ParametersList;
 import com.intellij.execution.configurations.ParamsGroup;
-import com.intellij.execution.filters.Filter;
 import com.intellij.execution.process.ProcessAdapter;
 import com.intellij.execution.process.ProcessEvent;
 import com.intellij.execution.process.ProcessHandler;
@@ -33,7 +32,6 @@
 import org.jetbrains.annotations.Nullable;
 
 import javax.swing.*;
-import java.util.Collections;
 
 /**
  * User : catherine
@@ -43,7 +41,7 @@
 
   public RestCommandLineState(RestRunConfiguration configuration,
                               ExecutionEnvironment env) {
-    super(configuration, env, Collections.<Filter>emptyList());
+    super(configuration, env);
     myConfiguration = configuration;
   }
 
diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml
index 88280cc..a27eb44 100644
--- a/python/src/META-INF/python-core.xml
+++ b/python/src/META-INF/python-core.xml
@@ -528,6 +528,12 @@
     <!-- Packaging -->
     <moduleService serviceInterface="com.jetbrains.python.packaging.PyPackageRequirementsSettings"
                    serviceImplementation="com.jetbrains.python.packaging.PyPackageRequirementsSettings"/>
+
+    <!-- Console -->
+    <toolWindow id="Python Console" anchor="bottom" icon=""
+                factoryClass="com.jetbrains.python.console.PythonConsoleToolWindowFactory" secondary="false"/>
+
+
   </extensions>
 
   <extensionPoints>    
@@ -615,6 +621,12 @@
     </component>
   </project-components>
 
+  <project-components>
+    <component>
+      <implementation-class>com.jetbrains.python.console.PythonConsoleToolWindow</implementation-class>
+    </component>
+  </project-components>
+
   <actions>
     <group id="PyTypeHierarchyPopupMenu">
       <reference ref="TypeHierarchyBase.BaseOnThisType"/>
diff --git a/python/src/com/jetbrains/python/PyAddImportFix.java b/python/src/com/jetbrains/python/PyAddImportFix.java
new file mode 100644
index 0000000..c13fed1
--- /dev/null
+++ b/python/src/com/jetbrains/python/PyAddImportFix.java
@@ -0,0 +1,55 @@
+package com.jetbrains.python;
+
+import com.intellij.codeInspection.LocalQuickFix;
+import com.intellij.codeInspection.ProblemDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.codeInsight.imports.AddImportHelper;
+import com.jetbrains.python.psi.LanguageLevel;
+import com.jetbrains.python.psi.PyElementGenerator;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyImportStatementBase;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Quick fix that adds import to file
+ *
+ * @author Ilya.Kazakevich
+ */
+public class PyAddImportFix implements LocalQuickFix {
+  @NotNull
+  private final String myImportToAdd;
+  @NotNull
+  private final PyFile myFile;
+
+  /**
+   * @param importToAdd string representing what to add (i.e. "from foo import bar")
+   * @param file where to add
+   */
+  public PyAddImportFix(@NotNull final String importToAdd, @NotNull final PyFile file) {
+    myImportToAdd = importToAdd;
+    myFile = file;
+  }
+
+  @NotNull
+  @Override
+  public String getName() {
+    return PyBundle.message("QFIX.add.import", myImportToAdd);
+  }
+
+  @NotNull
+  @Override
+  public String getFamilyName() {
+    return getName();
+  }
+
+  @Override
+  public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
+    final PyElementGenerator generator = PyElementGenerator.getInstance(project);
+    final PyImportStatementBase statement =
+      generator.createFromText(LanguageLevel.forElement(myFile), PyImportStatementBase.class, myImportToAdd);
+    final PsiElement recommendedPosition = AddImportHelper.getFileInsertPosition(myFile);
+    myFile.addAfter(statement, recommendedPosition);
+  }
+}
+
diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index 5d29d3f..482daa8 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -32,6 +32,8 @@
 
 QFIX.add.encoding=Add encoding declaration
 
+QFIX.add.import=Add "''{0}''"
+
 QFIX.NAME.parameters=Parameters of functions and methods
 QFIX.rename.parameter.to.$0=Rename to ''{0}''
 
diff --git a/python/src/com/jetbrains/python/actions/ExecuteInConsoleAction.java b/python/src/com/jetbrains/python/actions/ExecuteInConsoleAction.java
index 58b35fe..cb90d49 100644
--- a/python/src/com/jetbrains/python/actions/ExecuteInConsoleAction.java
+++ b/python/src/com/jetbrains/python/actions/ExecuteInConsoleAction.java
@@ -17,7 +17,6 @@
 
 import com.intellij.execution.ExecutionHelper;
 import com.intellij.execution.console.LanguageConsoleView;
-import com.intellij.execution.console.LanguageConsoleViewImpl;
 import com.intellij.execution.process.ProcessHandler;
 import com.intellij.execution.ui.RunContentDescriptor;
 import com.intellij.openapi.actionSystem.*;
@@ -215,7 +214,7 @@
   private static void startConsole(final Project project,
                                    final Consumer<PyCodeExecutor> consumer,
                                    Module context) {
-    PydevConsoleRunner runner = RunPythonConsoleAction.runPythonConsole(project, context);
+    PydevConsoleRunner runner = RunPythonConsoleAction.runPythonConsole(project, context, null);
     runner.addConsoleListener(new PydevConsoleRunner.ConsoleListener() {
       @Override
       public void handleConsoleInitialized(LanguageConsoleView consoleView) {
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/PySmartEnterProcessor.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/PySmartEnterProcessor.java
index e27f2a6..2785b23 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/PySmartEnterProcessor.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/PySmartEnterProcessor.java
@@ -79,7 +79,7 @@
     }
 
     final PsiElement[] children = element.getChildren();
-    for (PsiElement child : children) {
+    for (final PsiElement child : children) {
       if (element instanceof PyStatement && child instanceof PyStatement) {
         continue;
       }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/enterProcessors/PyPlainEnterProcessor.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/enterProcessors/PyPlainEnterProcessor.java
index ac2e312..ccdcf1b 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/enterProcessors/PyPlainEnterProcessor.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/enterProcessors/PyPlainEnterProcessor.java
@@ -20,7 +20,9 @@
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.util.PsiTreeUtil;
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.SmartEnterUtil;
-import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.PyStatementList;
+import com.jetbrains.python.psi.PyStatementListContainer;
+import com.jetbrains.python.psi.PyStatementPart;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -32,17 +34,8 @@
 public class PyPlainEnterProcessor implements EnterProcessor {
   @Nullable
   private static PyStatementList getStatementList(PsiElement psiElement, Editor editor) {
-    if (psiElement instanceof PyStatementPart) {
-      return ((PyStatementPart)psiElement).getStatementList();
-    }
-    else if (psiElement instanceof PyFunction) {
-      return ((PyFunction)psiElement).getStatementList();
-    }
-    else if (psiElement instanceof PyClass) {
-      return ((PyClass)psiElement).getStatementList();
-    }
-    else if (psiElement instanceof PyWithStatement) {
-      return PsiTreeUtil.getChildOfType(psiElement, PyStatementList.class);
+    if (psiElement instanceof PyStatementListContainer) {
+      return ((PyStatementListContainer)psiElement).getStatementList();
     }
     else {
       final CaretModel caretModel = editor.getCaretModel();
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyArgumentListFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyArgumentListFixer.java
index f7d877e..de90fa1 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyArgumentListFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyArgumentListFixer.java
@@ -25,26 +25,30 @@
 import com.jetbrains.python.psi.PyClass;
 import com.jetbrains.python.psi.PyDecorator;
 import com.jetbrains.python.psi.PyUtil;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * @author Alexey.Ivanov
  */
-public class PyArgumentListFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
-    if (psiElement instanceof PyArgumentList) {
-      final PsiElement rBrace = PyUtil.getChildByFilter(psiElement, PyTokenTypes.CLOSE_BRACES, 0);
-      if (psiElement.getParent() instanceof PyClass || psiElement.getParent() instanceof PyDecorator) {
-        final PsiElement lBrace = PyUtil.getChildByFilter(psiElement, PyTokenTypes.OPEN_BRACES, 0);
-        if (lBrace != null && rBrace == null) {
-          final Document document = editor.getDocument();
-          document.insertString(psiElement.getTextRange().getEndOffset(), ")");
-        }
+public class PyArgumentListFixer extends PyFixer<PyArgumentList> {
+  public PyArgumentListFixer() {
+    super(PyArgumentList.class);
+  }
+
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyArgumentList arguments) throws IncorrectOperationException {
+    final PsiElement rBrace = PyUtil.getChildByFilter(arguments, PyTokenTypes.CLOSE_BRACES, 0);
+    if (arguments.getParent() instanceof PyClass || arguments.getParent() instanceof PyDecorator) {
+      final PsiElement lBrace = PyUtil.getChildByFilter(arguments, PyTokenTypes.OPEN_BRACES, 0);
+      if (lBrace != null && rBrace == null) {
+        final Document document = editor.getDocument();
+        document.insertString(arguments.getTextRange().getEndOffset(), ")");
       }
-      else {
-        if (rBrace == null) {
-          final Document document = editor.getDocument();
-          document.insertString(psiElement.getTextRange().getEndOffset(), ")");
-        }
+    }
+    else {
+      if (rBrace == null) {
+        final Document document = editor.getDocument();
+        document.insertString(arguments.getTextRange().getEndOffset(), ")");
       }
     }
   }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyClassFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyClassFixer.java
index b9846e1..ce51d6d 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyClassFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyClassFixer.java
@@ -17,7 +17,6 @@
 
 import com.intellij.openapi.editor.Editor;
 import com.intellij.psi.PsiElement;
-import com.intellij.psi.tree.TokenSet;
 import com.intellij.psi.util.PsiTreeUtil;
 import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.PyTokenTypes;
@@ -25,6 +24,9 @@
 import com.jetbrains.python.psi.PyArgumentList;
 import com.jetbrains.python.psi.PyClass;
 import com.jetbrains.python.psi.PyUtil;
+import org.jetbrains.annotations.NotNull;
+
+import static com.jetbrains.python.psi.PyUtil.sure;
 
 /**
  * Created by IntelliJ IDEA.
@@ -32,21 +34,22 @@
  * Date:   16.04.2010
  * Time:   18:41:08
  */
-public class PyClassFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
-    if (psiElement instanceof PyClass) {
-      final PsiElement colon = PyUtil.getChildByFilter(psiElement, TokenSet.create(PyTokenTypes.COLON), 0);
-      if (colon == null) {
-        final PyClass aClass = (PyClass)psiElement;
-        final PyArgumentList argList = PsiTreeUtil.getChildOfType(aClass, PyArgumentList.class);
-        int offset = argList.getTextRange().getEndOffset();
-        String textToInsert = ":";
-        if (aClass.getNameNode() == null) {
-          processor.registerUnresolvedError(argList.getTextRange().getEndOffset() + 1);
-          textToInsert = " :";
-        }
-        editor.getDocument().insertString(offset, textToInsert);
+public class PyClassFixer extends PyFixer<PyClass> {
+  public PyClassFixer() {
+    super(PyClass.class);
+  }
+
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyClass pyClass) throws IncorrectOperationException {
+    final PsiElement colon = PyUtil.getFirstChildOfType(pyClass, PyTokenTypes.COLON);
+    if (colon == null) {
+      final PyArgumentList argList = PsiTreeUtil.getChildOfType(pyClass, PyArgumentList.class);
+      final int offset = sure(argList).getTextRange().getEndOffset();
+      String textToInsert = ":";
+      if (pyClass.getNameNode() == null) {
+        processor.registerUnresolvedError(argList.getTextRange().getEndOffset() + 1);
+        textToInsert = " :";
       }
+      editor.getDocument().insertString(offset, textToInsert);
     }
   }
 }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyConditionalStatementPartFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyConditionalStatementPartFixer.java
index 2885c63..f71c612 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyConditionalStatementPartFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyConditionalStatementPartFixer.java
@@ -25,6 +25,9 @@
 import com.jetbrains.python.psi.PyConditionalStatementPart;
 import com.jetbrains.python.psi.PyExpression;
 import com.jetbrains.python.psi.PyUtil;
+import org.jetbrains.annotations.NotNull;
+
+import static com.jetbrains.python.psi.PyUtil.sure;
 
 /**
  * Created by IntelliJ IDEA.
@@ -32,31 +35,35 @@
  * Date:   15.04.2010
  * Time:   19:33:14
  */
-public class PyConditionalStatementPartFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
-    if (psiElement instanceof PyConditionalStatementPart) {
-      final PyConditionalStatementPart conditionalStatementPart = (PyConditionalStatementPart)psiElement;
-      final PyExpression condition = conditionalStatementPart.getCondition();
-      final Document document = editor.getDocument();
-      final PsiElement colon = PyUtil.getChildByFilter(conditionalStatementPart, TokenSet.create(PyTokenTypes.COLON), 0);
-      if (colon == null) {
-        if (condition != null) {
-          final PsiElement firstNonComment = PyUtil.getFirstNonCommentAfter(condition.getNextSibling());
-          if (firstNonComment != null && !":".equals(firstNonComment.getNode().getText())) {
-            document.insertString(firstNonComment.getTextRange().getEndOffset(), ":");
-          }
+public class PyConditionalStatementPartFixer extends PyFixer<PyConditionalStatementPart> {
+  public PyConditionalStatementPartFixer() {
+    super(PyConditionalStatementPart.class);
+  }
+
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyConditionalStatementPart statementPart)
+    throws IncorrectOperationException {
+    final PyExpression condition = statementPart.getCondition();
+    final Document document = editor.getDocument();
+    final PsiElement colon = PyUtil.getFirstChildOfType(statementPart, PyTokenTypes.COLON);
+    if (colon == null) {
+      if (condition != null) {
+        final PsiElement firstNonComment = PyUtil.getFirstNonCommentAfter(condition.getNextSibling());
+        if (firstNonComment != null && !":".equals(firstNonComment.getNode().getText())) {
+          document.insertString(firstNonComment.getTextRange().getEndOffset(), ":");
         }
-        else {
-          final PsiElement keywordToken = PyUtil.getChildByFilter(conditionalStatementPart,
-                                                                  TokenSet.create(PyTokenTypes.IF_KEYWORD, PyTokenTypes.ELIF_KEYWORD,
-                                                                                  PyTokenTypes.WHILE_KEYWORD), 0);
-          final int offset = keywordToken.getTextRange().getEndOffset();
-          document.insertString(offset, " :");
-          processor.registerUnresolvedError(offset + 1);
-        }
-      } else if (condition == null) {
-          processor.registerUnresolvedError(colon.getTextRange().getStartOffset());
       }
+      else {
+        final TokenSet keywords = TokenSet.create(PyTokenTypes.IF_KEYWORD, PyTokenTypes.ELIF_KEYWORD, PyTokenTypes.WHILE_KEYWORD);
+        final PsiElement keywordToken = PyUtil.getChildByFilter(statementPart,
+                                                                keywords, 0);
+        final int offset = sure(keywordToken).getTextRange().getEndOffset();
+        document.insertString(offset, " :");
+        processor.registerUnresolvedError(offset + 1);
+      }
+    }
+    else if (condition == null) {
+      processor.registerUnresolvedError(colon.getTextRange().getStartOffset());
     }
   }
 }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyExceptFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyExceptFixer.java
index 8e3532c..67d5ff5 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyExceptFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyExceptFixer.java
@@ -17,13 +17,15 @@
 
 import com.intellij.openapi.editor.Editor;
 import com.intellij.psi.PsiElement;
-import com.intellij.psi.tree.TokenSet;
 import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.PyTokenTypes;
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
 import com.jetbrains.python.psi.PyExceptPart;
 import com.jetbrains.python.psi.PyExpression;
 import com.jetbrains.python.psi.PyUtil;
+import org.jetbrains.annotations.NotNull;
+
+import static com.jetbrains.python.psi.PyUtil.sure;
 
 /**
  * Created by IntelliJ IDEA.
@@ -31,24 +33,26 @@
  * Date:   22.04.2010
  * Time:   18:13:34
  */
-public class PyExceptFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
-    if (psiElement instanceof PyExceptPart) {
-      PyExceptPart exceptPart = (PyExceptPart)psiElement;
-      final PsiElement colon = PyUtil.getChildByFilter(exceptPart, TokenSet.create(PyTokenTypes.COLON), 0);
-      if (colon == null) {
-        int offset = PyUtil.getChildByFilter(exceptPart,
-                                             TokenSet.create(PyTokenTypes.EXCEPT_KEYWORD), 0).getTextRange().getEndOffset();
-        final PyExpression exceptClass = exceptPart.getExceptClass();
-        if (exceptClass != null) {
-          offset = exceptClass.getTextRange().getEndOffset();
-        }
-        final PyExpression target = exceptPart.getTarget();
-        if (target != null) {
-          offset = target.getTextRange().getEndOffset();
-        }
-        editor.getDocument().insertString(offset, ":");
+public class PyExceptFixer extends PyFixer<PyExceptPart> {
+  public PyExceptFixer() {
+    super(PyExceptPart.class);
+  }
+
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyExceptPart exceptPart) throws IncorrectOperationException {
+    final PsiElement colon = PyUtil.getFirstChildOfType(exceptPart, PyTokenTypes.COLON);
+    if (colon == null) {
+      final PsiElement exceptToken = PyUtil.getFirstChildOfType(exceptPart, PyTokenTypes.EXCEPT_KEYWORD);
+      int offset = sure(exceptToken).getTextRange().getEndOffset();
+      final PyExpression exceptClass = exceptPart.getExceptClass();
+      if (exceptClass != null) {
+        offset = exceptClass.getTextRange().getEndOffset();
       }
+      final PyExpression target = exceptPart.getTarget();
+      if (target != null) {
+        offset = target.getTextRange().getEndOffset();
+      }
+      editor.getDocument().insertString(offset, ":");
     }
   }
 }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyFixer.java
index f02831d..0ebdceb 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyFixer.java
@@ -19,6 +19,8 @@
 import com.intellij.psi.PsiElement;
 import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
+import com.jetbrains.python.psi.PyElement;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Created by IntelliJ IDEA.
@@ -26,6 +28,20 @@
  * Date:   15.04.2010
  * Time:   17:10:33
  */
-public interface PyFixer {
-    void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException;
+public abstract class PyFixer<T extends PyElement> {
+  private final Class<T> myClass;
+
+  public PyFixer(@NotNull Class<T> aClass) {
+    myClass = aClass;
+  }
+
+  public final void apply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PsiElement element)
+    throws IncorrectOperationException {
+    if (myClass.isInstance(element)) {
+      //noinspection unchecked
+      doApply(editor, processor, (T)element);
+    }
+  }
+
+  public abstract void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull T element);
 }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyForPartFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyForPartFixer.java
index 1b8b438..eefe5cd 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyForPartFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyForPartFixer.java
@@ -18,12 +18,13 @@
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.psi.PsiElement;
-import com.intellij.psi.tree.TokenSet;
-import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.PyTokenTypes;
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
 import com.jetbrains.python.psi.PyForPart;
 import com.jetbrains.python.psi.PyUtil;
+import org.jetbrains.annotations.NotNull;
+
+import static com.jetbrains.python.psi.PyUtil.sure;
 
 /**
  * Created by IntelliJ IDEA.
@@ -31,41 +32,42 @@
  * Date:   16.04.2010
  * Time:   16:03:43
  */
-public class PyForPartFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
-    if (psiElement instanceof PyForPart) {
-      final PyForPart forPart = (PyForPart)psiElement;
-      final PsiElement colon = PyUtil.getChildByFilter(psiElement, TokenSet.create(PyTokenTypes.COLON), 0);
-      final Document document = editor.getDocument();
-      final PsiElement forToken = PyUtil.getChildByFilter(forPart,
-                                                          TokenSet.create(PyTokenTypes.FOR_KEYWORD), 0);
-      if (colon == null) {
-        String textToInsert = ":";
-        PsiElement sourceOrTarget = forPart.getSource();
-        PsiElement positionToInsert = sourceOrTarget;
-        if (sourceOrTarget == null) {
-          sourceOrTarget = forPart.getTarget();
-          final PsiElement inToken = PyUtil.getChildByFilter(forPart, TokenSet.create(PyTokenTypes.IN_KEYWORD), 0);
-          if (inToken == null) {
-            if (sourceOrTarget == null) {
-              positionToInsert = forToken;
-              textToInsert = "  in :";
-              processor.registerUnresolvedError(positionToInsert.getTextRange().getEndOffset() + 1);
-            }
-            else {
-              positionToInsert = sourceOrTarget;
-              textToInsert = " in :";
-              processor.registerUnresolvedError(positionToInsert.getTextRange().getEndOffset() + 4);
-            }
-          }
-          else {
-            positionToInsert = inToken;
-            textToInsert = " :";
+public class PyForPartFixer extends PyFixer<PyForPart> {
+  public PyForPartFixer() {
+    super(PyForPart.class);
+  }
+
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyForPart forPart) {
+    final PsiElement colon = PyUtil.getFirstChildOfType(forPart, PyTokenTypes.COLON);
+    final Document document = editor.getDocument();
+    final PsiElement forToken = PyUtil.getFirstChildOfType(forPart, PyTokenTypes.FOR_KEYWORD);
+    if (colon == null) {
+      String textToInsert = ":";
+      PsiElement sourceOrTarget = forPart.getSource();
+      PsiElement positionToInsert = sourceOrTarget;
+      if (sourceOrTarget == null) {
+        sourceOrTarget = forPart.getTarget();
+        final PsiElement inToken = PyUtil.getFirstChildOfType(forPart, PyTokenTypes.IN_KEYWORD);
+        if (inToken == null) {
+          if (sourceOrTarget == null) {
+            positionToInsert = sure(forToken);
+            textToInsert = "  in :";
             processor.registerUnresolvedError(positionToInsert.getTextRange().getEndOffset() + 1);
           }
+          else {
+            positionToInsert = sourceOrTarget;
+            textToInsert = " in :";
+            processor.registerUnresolvedError(positionToInsert.getTextRange().getEndOffset() + 4);
+          }
         }
-        document.insertString(positionToInsert.getTextRange().getEndOffset(), textToInsert);
+        else {
+          positionToInsert = inToken;
+          textToInsert = " :";
+          processor.registerUnresolvedError(positionToInsert.getTextRange().getEndOffset() + 1);
+        }
       }
+      document.insertString(positionToInsert.getTextRange().getEndOffset(), textToInsert);
     }
   }
 }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyFunctionFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyFunctionFixer.java
index f1988e1..ad959f8 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyFunctionFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyFunctionFixer.java
@@ -18,13 +18,13 @@
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.psi.PsiElement;
-import com.intellij.psi.tree.TokenSet;
 import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.PyTokenTypes;
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
 import com.jetbrains.python.psi.PyFunction;
 import com.jetbrains.python.psi.PyParameterList;
 import com.jetbrains.python.psi.PyUtil;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Created by IntelliJ IDEA.
@@ -32,16 +32,19 @@
  * Date:   16.04.2010
  * Time:   16:59:07
  */
-public class PyFunctionFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
-    if (psiElement instanceof PyFunction) {
-      final PsiElement colon = PyUtil.getChildByFilter(psiElement, TokenSet.create(PyTokenTypes.COLON), 0);
-      if (colon == null) {
-        final PyFunction function = (PyFunction)psiElement;
-        final PyParameterList parameterList = function.getParameterList();
-        final Document document = editor.getDocument();
-        document.insertString(parameterList.getTextRange().getEndOffset(), ":");
-      }
+public class PyFunctionFixer extends PyFixer<PyFunction> {
+  public PyFunctionFixer() {
+    super(PyFunction.class);
+  }
+
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyFunction function)
+    throws IncorrectOperationException {
+    final PsiElement colon = PyUtil.getFirstChildOfType(function, PyTokenTypes.COLON);
+    if (colon == null) {
+      final PyParameterList parameterList = function.getParameterList();
+      final Document document = editor.getDocument();
+      document.insertString(parameterList.getTextRange().getEndOffset(), ":");
     }
   }
 }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyMissingBracesFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyMissingBracesFixer.java
index 0d8565d..eb97d2b 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyMissingBracesFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyMissingBracesFixer.java
@@ -20,6 +20,7 @@
 import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
 import com.jetbrains.python.psi.*;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Created by IntelliJ IDEA.
@@ -27,10 +28,16 @@
  * Date:   15.04.2010
  * Time:   17:55:46
  */
-public class PyMissingBracesFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
+public class PyMissingBracesFixer extends PyFixer<PyElement> {
+  public PyMissingBracesFixer() {
+    super(PyElement.class);
+  }
+
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyElement psiElement)
+    throws IncorrectOperationException {
     if (psiElement instanceof PySetLiteralExpression || psiElement instanceof PyDictLiteralExpression) {
-      PsiElement lastChild = PyUtil.getFirstNonCommentBefore(psiElement.getLastChild());
+      final PsiElement lastChild = PyUtil.getFirstNonCommentBefore(psiElement.getLastChild());
       if (lastChild != null && !"}".equals(lastChild.getText())) {
         editor.getDocument().insertString(lastChild.getTextRange().getEndOffset(), "}");
       }
@@ -38,7 +45,7 @@
     else if (psiElement instanceof PyListLiteralExpression ||
              psiElement instanceof PySliceExpression ||
              psiElement instanceof PySubscriptionExpression) {
-      PsiElement lastChild = PyUtil.getFirstNonCommentBefore(psiElement.getLastChild());
+      final PsiElement lastChild = PyUtil.getFirstNonCommentBefore(psiElement.getLastChild());
       if (lastChild != null && !"]".equals(lastChild.getText())) {
         editor.getDocument().insertString(lastChild.getTextRange().getEndOffset(), "]");
       }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyParameterListFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyParameterListFixer.java
index 7b5970d..69a06b8 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyParameterListFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyParameterListFixer.java
@@ -23,6 +23,7 @@
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
 import com.jetbrains.python.psi.PyParameterList;
 import com.jetbrains.python.psi.PyUtil;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Created by IntelliJ IDEA.
@@ -30,19 +31,22 @@
  * Date:   16.04.2010
  * Time:   17:25:46
  */
-public class PyParameterListFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
-    if (psiElement instanceof PyParameterList) {
-      final PsiElement lBrace = PyUtil.getChildByFilter(psiElement, PyTokenTypes.OPEN_BRACES, 0);
-      final PsiElement rBrace = PyUtil.getChildByFilter(psiElement, PyTokenTypes.CLOSE_BRACES, 0);
-      if (lBrace == null || rBrace == null) {
-        final Document document = editor.getDocument();
-        if (lBrace == null) {
-          document.insertString(psiElement.getTextRange().getStartOffset(), "(");
-        }
-        else {
-          document.insertString(psiElement.getTextRange().getEndOffset(), ")");
-        }
+public class PyParameterListFixer extends PyFixer<PyParameterList> {
+  public PyParameterListFixer() {
+    super(PyParameterList.class);
+  }
+
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyParameterList psiElement) throws IncorrectOperationException {
+    final PsiElement lBrace = PyUtil.getChildByFilter(psiElement, PyTokenTypes.OPEN_BRACES, 0);
+    final PsiElement rBrace = PyUtil.getChildByFilter(psiElement, PyTokenTypes.CLOSE_BRACES, 0);
+    if (lBrace == null || rBrace == null) {
+      final Document document = editor.getDocument();
+      if (lBrace == null) {
+        document.insertString(psiElement.getTextRange().getStartOffset(), "(");
+      }
+      else {
+        document.insertString(psiElement.getTextRange().getEndOffset(), ")");
       }
     }
   }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyParenthesizedFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyParenthesizedFixer.java
index a055375..582e034 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyParenthesizedFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyParenthesizedFixer.java
@@ -20,6 +20,7 @@
 import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
 import com.jetbrains.python.psi.PyParenthesizedExpression;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Created by IntelliJ IDEA.
@@ -27,13 +28,17 @@
  * Date:   15.04.2010
  * Time:   17:42:08
  */
-public class PyParenthesizedFixer implements PyFixer {
-  public void apply(final Editor editor, final PySmartEnterProcessor processor, final PsiElement psiElement) throws IncorrectOperationException {
-    if (psiElement instanceof PyParenthesizedExpression) {
-      final PsiElement lastChild = psiElement.getLastChild();
-      if (lastChild != null && !")".equals(lastChild.getText())) {
-        editor.getDocument().insertString(psiElement.getTextRange().getEndOffset(), ")");
-      }
+public class PyParenthesizedFixer extends PyFixer<PyParenthesizedExpression> {
+  public PyParenthesizedFixer() {
+    super(PyParenthesizedExpression.class);
+  }
+
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyParenthesizedExpression expression)
+    throws IncorrectOperationException {
+    final PsiElement lastChild = expression.getLastChild();
+    if (lastChild != null && !")".equals(lastChild.getText())) {
+      editor.getDocument().insertString(expression.getTextRange().getEndOffset(), ")");
     }
   }
 }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyStringLiteralFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyStringLiteralFixer.java
index 906aecd..3e9925a 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyStringLiteralFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyStringLiteralFixer.java
@@ -17,10 +17,10 @@
 
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.psi.PsiElement;
 import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
 import com.jetbrains.python.psi.PyStringLiteralExpression;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Created by IntelliJ IDEA.
@@ -28,31 +28,35 @@
  * Date:   15.04.2010
  * Time:   17:17:14
  */
-public class PyStringLiteralFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
-    if (psiElement instanceof PyStringLiteralExpression) {
-      final String text = psiElement.getText();
-      if (StringUtil.startsWith(text, "\"\"\"")) {
-        final int suffixLength = StringUtil.commonSuffixLength(text, "\"\"\"");
-        if (suffixLength != 3) {
-          editor.getDocument().insertString(psiElement.getTextRange().getEndOffset(), "\"\"\"".substring(suffixLength));
-        }
+public class PyStringLiteralFixer extends PyFixer<PyStringLiteralExpression> {
+  public PyStringLiteralFixer() {
+    super(PyStringLiteralExpression.class);
+  }
+
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyStringLiteralExpression psiElement)
+    throws IncorrectOperationException {
+    final String text = psiElement.getText();
+    if (StringUtil.startsWith(text, "\"\"\"")) {
+      final int suffixLength = StringUtil.commonSuffixLength(text, "\"\"\"");
+      if (suffixLength != 3) {
+        editor.getDocument().insertString(psiElement.getTextRange().getEndOffset(), "\"\"\"".substring(suffixLength));
       }
-      else if (StringUtil.startsWith(text, "\'\'\'")) {
-        final int suffixLength = StringUtil.commonSuffixLength(text, "\'\'\'");
-        if (suffixLength != 3) {
-          editor.getDocument().insertString(psiElement.getTextRange().getEndOffset(), "\'\'\'".substring(suffixLength));
-        }
+    }
+    else if (StringUtil.startsWith(text, "\'\'\'")) {
+      final int suffixLength = StringUtil.commonSuffixLength(text, "\'\'\'");
+      if (suffixLength != 3) {
+        editor.getDocument().insertString(psiElement.getTextRange().getEndOffset(), "\'\'\'".substring(suffixLength));
       }
-      else if (StringUtil.startsWith(text, "\"")) {
-        if (!StringUtil.endsWith(text, "\"")) {
-          editor.getDocument().insertString(psiElement.getTextRange().getEndOffset(), "\"");
-        }
+    }
+    else if (StringUtil.startsWith(text, "\"")) {
+      if (!StringUtil.endsWith(text, "\"")) {
+        editor.getDocument().insertString(psiElement.getTextRange().getEndOffset(), "\"");
       }
-      else if (StringUtil.startsWith(text, "\'")) {
-        if (!StringUtil.endsWith(text, "\'")) {
-          editor.getDocument().insertString(psiElement.getTextRange().getEndOffset(), "\'");
-        }
+    }
+    else if (StringUtil.startsWith(text, "\'")) {
+      if (!StringUtil.endsWith(text, "\'")) {
+        editor.getDocument().insertString(psiElement.getTextRange().getEndOffset(), "\'");
       }
     }
   }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyUnconditionalStatementPartFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyUnconditionalStatementPartFixer.java
index 57bfd39..6aace39 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyUnconditionalStatementPartFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyUnconditionalStatementPartFixer.java
@@ -21,10 +21,10 @@
 import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.PyTokenTypes;
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
-import com.jetbrains.python.psi.PyElsePart;
-import com.jetbrains.python.psi.PyFinallyPart;
-import com.jetbrains.python.psi.PyTryPart;
-import com.jetbrains.python.psi.PyUtil;
+import com.jetbrains.python.psi.*;
+import org.jetbrains.annotations.NotNull;
+
+import static com.jetbrains.python.psi.PyUtil.sure;
 
 /**
  * Created by IntelliJ IDEA.
@@ -32,16 +32,20 @@
  * Date:   16.04.2010
  * Time:   14:25:20
  */
-public class PyUnconditionalStatementPartFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
+public class PyUnconditionalStatementPartFixer extends PyFixer<PyElement> {
+  public PyUnconditionalStatementPartFixer() {
+    super(PyElement.class);
+  }
+
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyElement psiElement)
+    throws IncorrectOperationException {
     if (PyUtil.instanceOf(psiElement, PyElsePart.class, PyTryPart.class, PyFinallyPart.class)) {
-      final PsiElement colon = PyUtil.getChildByFilter(psiElement, TokenSet.create(PyTokenTypes.COLON), 0);
+      final PsiElement colon = PyUtil.getFirstChildOfType(psiElement, PyTokenTypes.COLON);
       if (colon == null) {
-        final PsiElement keywordToken = PyUtil.getChildByFilter(psiElement,
-                                                                TokenSet.create(PyTokenTypes.ELSE_KEYWORD, PyTokenTypes.TRY_KEYWORD,
-                                                                                PyTokenTypes.FINALLY_KEYWORD),
-                                                                0);
-        editor.getDocument().insertString(keywordToken.getTextRange().getEndOffset(), ":");
+        final TokenSet keywords = TokenSet.create(PyTokenTypes.ELSE_KEYWORD, PyTokenTypes.TRY_KEYWORD, PyTokenTypes.FINALLY_KEYWORD);
+        final PsiElement keywordToken = PyUtil.getChildByFilter(psiElement, keywords, 0);
+        editor.getDocument().insertString(sure(keywordToken).getTextRange().getEndOffset(), ":");
       }
     }
   }
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java
index b6d099e..ec236d9 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java
@@ -15,65 +15,59 @@
  */
 package com.jetbrains.python.codeInsight.editorActions.smartEnter.fixers;
 
-import com.intellij.lang.ASTNode;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.psi.PsiElement;
 import com.intellij.util.IncorrectOperationException;
 import com.jetbrains.python.PyTokenTypes;
 import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
-import com.jetbrains.python.psi.PyElementType;
 import com.jetbrains.python.psi.PyExpression;
+import com.jetbrains.python.psi.PyUtil;
 import com.jetbrains.python.psi.PyWithItem;
 import com.jetbrains.python.psi.PyWithStatement;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 import static com.jetbrains.python.psi.PyUtil.sure;
 
 /**
  * @author Mikhail Golubev
  */
-public class PyWithFixer implements PyFixer {
-  public void apply(Editor editor, PySmartEnterProcessor processor, PsiElement psiElement) throws IncorrectOperationException {
-    if (psiElement instanceof PyWithStatement) {
-      final PyWithStatement withStatement = (PyWithStatement)psiElement;
-      final PsiElement colonToken = getFirstChildOfType(psiElement, PyTokenTypes.COLON);
-      final PsiElement withToken = getFirstChildOfType(withStatement, PyTokenTypes.WITH_KEYWORD);
-      final Document document = editor.getDocument();
-      if (colonToken == null) {
-        int insertAt = sure(withToken).getTextRange().getEndOffset();
-        String textToInsert = ":";
-        final PyWithItem[] withItems = withStatement.getWithItems();
-        final PyWithItem lastItem = withItems.length != 0 ? withItems[withItems.length - 1] : null;
-        if (lastItem == null || lastItem.getExpression() == null) {
-          textToInsert = " :";
-          processor.registerUnresolvedError(insertAt + 1);
-        }
-        else {
-          final PyExpression expression = lastItem.getExpression();
-          insertAt = expression.getTextRange().getEndOffset();
-          final PsiElement asToken = getFirstChildOfType(lastItem, PyTokenTypes.AS_KEYWORD);
-          if (asToken != null) {
-            insertAt = asToken.getTextRange().getEndOffset();
-            final PyExpression target = lastItem.getTarget();
-            if (target != null) {
-              insertAt = target.getTextRange().getEndOffset();
-            }
-            else {
-              textToInsert = " :";
-              processor.registerUnresolvedError(insertAt + 1);
-            }
-          }
-        }
-        document.insertString(insertAt, textToInsert);
-      }
-    }
+public class PyWithFixer extends PyFixer<PyWithStatement> {
+  public PyWithFixer() {
+    super(PyWithStatement.class);
   }
 
-  @Nullable
-  private static PsiElement getFirstChildOfType(@NotNull final PsiElement element, @NotNull PyElementType type) {
-    final ASTNode child = element.getNode().findChildByType(type);
-    return child != null ? child.getPsi() : null;
+  @Override
+  public void doApply(@NotNull Editor editor, @NotNull PySmartEnterProcessor processor, @NotNull PyWithStatement withStatement) throws IncorrectOperationException {
+    final PsiElement colonToken = PyUtil.getFirstChildOfType(withStatement, PyTokenTypes.COLON);
+    final PsiElement withToken = PyUtil.getFirstChildOfType(withStatement, PyTokenTypes.WITH_KEYWORD);
+    final Document document = editor.getDocument();
+    if (colonToken == null) {
+      int insertAt = sure(withToken).getTextRange().getEndOffset();
+      String textToInsert = ":";
+      final PyWithItem[] withItems = withStatement.getWithItems();
+      final PyWithItem lastItem = withItems.length != 0 ? withItems[withItems.length - 1] : null;
+      if (lastItem == null || lastItem.getExpression() == null) {
+        textToInsert = " :";
+        processor.registerUnresolvedError(insertAt + 1);
+      }
+      else {
+        final PyExpression expression = lastItem.getExpression();
+        insertAt = expression.getTextRange().getEndOffset();
+        final PsiElement asToken = PyUtil.getFirstChildOfType(lastItem, PyTokenTypes.AS_KEYWORD);
+        if (asToken != null) {
+          insertAt = asToken.getTextRange().getEndOffset();
+          final PyExpression target = lastItem.getTarget();
+          if (target != null) {
+            insertAt = target.getTextRange().getEndOffset();
+          }
+          else {
+            textToInsert = " :";
+            processor.registerUnresolvedError(insertAt + 1);
+          }
+        }
+      }
+      document.insertString(insertAt, textToInsert);
+    }
   }
 }
diff --git a/python/src/com/jetbrains/python/console/PydevConsoleRunner.java b/python/src/com/jetbrains/python/console/PydevConsoleRunner.java
index 3d77ac4..848818b 100644
--- a/python/src/com/jetbrains/python/console/PydevConsoleRunner.java
+++ b/python/src/com/jetbrains/python/console/PydevConsoleRunner.java
@@ -16,6 +16,10 @@
 package com.jetbrains.python.console;
 
 import com.google.common.base.CharMatcher;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.ExecutionHelper;
@@ -59,11 +63,14 @@
 import com.intellij.openapi.vfs.CharsetToolkit;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.encoding.EncodingManager;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowManager;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.impl.source.tree.FileElement;
 import com.intellij.remote.RemoteSshProcess;
 import com.intellij.testFramework.LightVirtualFile;
+import com.intellij.ui.content.Content;
 import com.intellij.util.ArrayUtil;
 import com.intellij.util.IJSwingUtilities;
 import com.intellij.util.PathMappingSettings;
@@ -132,6 +139,9 @@
 
   private static final long APPROPRIATE_TO_WAIT = 60000;
   private PyRemoteSdkCredentials myRemoteCredentials;
+  private ToolWindow myToolWindow;
+
+  private String myConsoleTitle = null;
 
   protected PydevConsoleRunner(@NotNull final Project project,
                                @NotNull Sdk sdk, @NotNull final PyConsoleType consoleType,
@@ -192,8 +202,10 @@
                                                 @NotNull final PyConsoleType consoleType,
                                                 @Nullable final String workingDirectory,
                                                 @NotNull final Map<String, String> environmentVariables,
+                                                @Nullable final ToolWindow toolWindow,
                                                 final String... statements2execute) {
     final PydevConsoleRunner consoleRunner = create(project, sdk, consoleType, workingDirectory, environmentVariables);
+    consoleRunner.setToolWindow(toolWindow);
     consoleRunner.setStatementsToExecute(statements2execute);
     consoleRunner.run();
     return consoleRunner;
@@ -481,6 +493,20 @@
     }
   }
 
+  @Override
+  protected String constructConsoleTitle(@NotNull String consoleTitle) {
+    if (myConsoleTitle == null) {
+      myConsoleTitle = super.constructConsoleTitle(consoleTitle);
+    }
+    return myConsoleTitle;
+  }
+
+  @Override
+  protected void showConsole(Executor defaultExecutor, RunContentDescriptor contentDescriptor) {
+    PythonConsoleToolWindow terminalView = PythonConsoleToolWindow.getInstance(getProject());
+    terminalView.init(getToolWindow(), contentDescriptor);
+  }
+
   protected AnAction createRerunAction() {
     return new RestartAction(this);
   }
@@ -583,9 +609,32 @@
   }
 
   @Override
-  protected AnAction createCloseAction(Executor defaultExecutor, RunContentDescriptor myDescriptor) {
-    final AnAction generalCloseAction = super.createCloseAction(defaultExecutor, myDescriptor);
-    return createConsoleStoppingAction(generalCloseAction);
+  protected AnAction createCloseAction(Executor defaultExecutor, final RunContentDescriptor descriptor) {
+    final AnAction generalCloseAction = super.createCloseAction(defaultExecutor, descriptor);
+
+    final AnAction stopAction = new DumbAwareAction() {
+      @Override
+      public void update(AnActionEvent e) {
+        generalCloseAction.update(e);
+      }
+
+      @Override
+      public void actionPerformed(AnActionEvent e) {
+        e = stopConsole(e);
+
+        clearContent(descriptor);
+
+        generalCloseAction.actionPerformed(e);
+      }
+    };
+    stopAction.copyFrom(generalCloseAction);
+    return stopAction;
+  }
+
+  private void clearContent(RunContentDescriptor descriptor) {
+    Content content = getToolWindow().getContentManager().findContent(descriptor.getDisplayName());
+    assert content != null;
+    getToolWindow().getContentManager().removeContent(content, true);
   }
 
   private AnAction createConsoleStoppingAction(final AnAction generalStopAction) {
@@ -597,26 +646,31 @@
 
       @Override
       public void actionPerformed(AnActionEvent e) {
-        if (myPydevConsoleCommunication != null) {
-          final AnActionEvent furtherActionEvent =
-            new AnActionEvent(e.getInputEvent(), e.getDataContext(), e.getPlace(),
-                              e.getPresentation(), e.getActionManager(), e.getModifiers());
-          try {
-            closeCommunication();
-            // waiting for REPL communication before destroying process handler
-            Thread.sleep(300);
-          }
-          catch (Exception ignored) {
-            // Ignore
-          }
-          generalStopAction.actionPerformed(furtherActionEvent);
-        }
+        e = stopConsole(e);
+
+        generalStopAction.actionPerformed(e);
       }
     };
     stopAction.copyFrom(generalStopAction);
     return stopAction;
   }
 
+  private AnActionEvent stopConsole(AnActionEvent e) {
+    if (myPydevConsoleCommunication != null) {
+      e = new AnActionEvent(e.getInputEvent(), e.getDataContext(), e.getPlace(),
+                            e.getPresentation(), e.getActionManager(), e.getModifiers());
+      try {
+        closeCommunication();
+        // waiting for REPL communication before destroying process handler
+        Thread.sleep(300);
+      }
+      catch (Exception ignored) {
+        // Ignore
+      }
+    }
+    return e;
+  }
+
   protected AnAction createSplitLineAction() {
 
     class ConsoleSplitLineAction extends EditorAction {
@@ -738,6 +792,17 @@
     }
   }
 
+  public ToolWindow getToolWindow() {
+    if (myToolWindow == null) {
+      myToolWindow = ToolWindowManager.getInstance(getProject()).getToolWindow(PythonConsoleToolWindowFactory.ID);
+    }
+    return myToolWindow;
+  }
+
+  public void setToolWindow(ToolWindow toolWindow) {
+    myToolWindow = toolWindow;
+  }
+
   public interface ConsoleListener {
     void handleConsoleInitialized(LanguageConsoleView consoleView);
   }
@@ -889,4 +954,21 @@
 
     return session;
   }
+
+  @Override
+  protected List<String> getActiveConsoleNames(final String consoleTitle) {
+    return FluentIterable.from(
+      Lists.newArrayList(PythonConsoleToolWindow.getInstance(getProject()).getToolWindow().getContentManager().getContents())).transform(
+      new Function<Content, String>() {
+        @Override
+        public String apply(Content input) {
+          return input.getDisplayName();
+        }
+      }).filter(new Predicate<String>() {
+      @Override
+      public boolean apply(String input) {
+        return input.contains(consoleTitle);
+      }
+    }).toList();
+  }
 }
diff --git a/python/src/com/jetbrains/python/console/PythonConsoleToolWindow.java b/python/src/com/jetbrains/python/console/PythonConsoleToolWindow.java
new file mode 100644
index 0000000..e8c50e4
--- /dev/null
+++ b/python/src/com/jetbrains/python/console/PythonConsoleToolWindow.java
@@ -0,0 +1,127 @@
+package com.jetbrains.python.console;
+
+import com.intellij.execution.ui.RunContentDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.SimpleToolWindowPanel;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
+import com.intellij.openapi.wm.ex.ToolWindowManagerListener;
+import com.intellij.openapi.wm.impl.content.ToolWindowContentUi;
+import com.intellij.ui.content.Content;
+import com.intellij.ui.content.ContentFactory;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+
+/**
+ * @author traff
+ */
+public class PythonConsoleToolWindow {
+
+  private final Project myProject;
+
+  private boolean myInitialized = false;
+
+  public PythonConsoleToolWindow(Project project) {
+    myProject = project;
+  }
+
+  public static PythonConsoleToolWindow getInstance(@NotNull Project project) {
+    return project.getComponent(PythonConsoleToolWindow.class);
+  }
+
+
+  public void init(final @NotNull ToolWindow toolWindow, final @NotNull RunContentDescriptor contentDescriptor) {
+    addContent(toolWindow, contentDescriptor);
+
+    if (!myInitialized) {
+      doInit(toolWindow);
+    }
+  }
+
+  private void doInit(final ToolWindow toolWindow) {
+    myInitialized = true;
+
+    toolWindow.setToHideOnEmptyContent(true);
+
+    ((ToolWindowManagerEx)ToolWindowManager.getInstance(myProject)).addToolWindowManagerListener(new ToolWindowManagerListener() {
+      @Override
+      public void toolWindowRegistered(@NotNull String id) {
+      }
+
+      @Override
+      public void stateChanged() {
+        ToolWindow window = getToolWindow();
+        if (window != null) {
+          boolean visible = window.isVisible();
+          if (visible && toolWindow.getContentManager().getContentCount() == 0) {
+            RunPythonConsoleAction.runPythonConsole(myProject, null, toolWindow);
+          }
+        }
+      }
+    });
+  }
+
+  private static void addContent(ToolWindow toolWindow, RunContentDescriptor contentDescriptor) {
+    toolWindow.getComponent().putClientProperty(ToolWindowContentUi.HIDE_ID_LABEL, "true");
+
+    Content content = toolWindow.getContentManager().findContent(contentDescriptor.getDisplayName());
+    if (content == null) {
+      content = createContent(contentDescriptor);
+      toolWindow.getContentManager().addContent(content);
+    }
+    else {
+      SimpleToolWindowPanel panel = new SimpleToolWindowPanel(false, true);
+      resetContent(contentDescriptor, panel, content);
+    }
+
+    toolWindow.getContentManager().setSelectedContent(content);
+  }
+
+  public ToolWindow getToolWindow() {
+    return ToolWindowManager.getInstance(myProject).getToolWindow(PythonConsoleToolWindowFactory.ID);
+  }
+
+  private static Content createContent(final @NotNull RunContentDescriptor contentDescriptor) {
+    SimpleToolWindowPanel panel = new SimpleToolWindowPanel(false, true);
+
+    final Content content = ContentFactory.SERVICE.getInstance().createContent(panel, contentDescriptor.getDisplayName(), false);
+    content.setCloseable(true);
+
+    resetContent(contentDescriptor, panel, content);
+
+    return content;
+  }
+
+  private static void resetContent(RunContentDescriptor contentDescriptor, SimpleToolWindowPanel panel, Content content) {
+    panel.setContent(contentDescriptor.getComponent());
+    //panel.addFocusListener(createFocusListener(toolWindow));
+
+    content.setComponent(panel);
+    content.setPreferredFocusableComponent(contentDescriptor.getComponent());
+  }
+
+  private static FocusListener createFocusListener(final ToolWindow toolWindow) {
+    return new FocusListener() {
+      @Override
+      public void focusGained(FocusEvent e) {
+        JComponent component = getComponentToFocus(toolWindow);
+        if (component != null) {
+          component.requestFocusInWindow();
+        }
+      }
+
+      @Override
+      public void focusLost(FocusEvent e) {
+
+      }
+    };
+  }
+
+  private static JComponent getComponentToFocus(ToolWindow window) {
+    return window.getContentManager().getComponent();
+  }
+}
diff --git a/python/src/com/jetbrains/python/console/PythonConsoleToolWindowFactory.java b/python/src/com/jetbrains/python/console/PythonConsoleToolWindowFactory.java
new file mode 100644
index 0000000..f042a53
--- /dev/null
+++ b/python/src/com/jetbrains/python/console/PythonConsoleToolWindowFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.console;
+
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowFactory;
+
+/**
+ * @author traff
+ */
+public class PythonConsoleToolWindowFactory implements ToolWindowFactory, DumbAware {
+  public static final String ID = "Python Console";
+
+  @Override
+  public void createToolWindowContent(Project project, ToolWindow toolWindow) {
+    RunPythonConsoleAction.runPythonConsole(project, null, toolWindow);
+  }
+}
diff --git a/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java b/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java
index 02a153f..566adea 100644
--- a/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java
+++ b/python/src/com/jetbrains/python/console/RunPythonConsoleAction.java
@@ -32,6 +32,7 @@
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.ToolWindow;
 import com.intellij.util.PathMappingSettings;
 import com.jetbrains.python.buildout.BuildoutFacet;
 import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
@@ -42,6 +43,7 @@
 import com.jetbrains.python.sdk.PythonSdkType;
 import icons.PythonIcons;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 import java.util.List;
@@ -77,11 +79,11 @@
 
   public void actionPerformed(final AnActionEvent e) {
     final Project project = e.getData(CommonDataKeys.PROJECT);
-    runPythonConsole(project, e.getData(LangDataKeys.MODULE));
+    runPythonConsole(project, e.getData(LangDataKeys.MODULE), null);
   }
 
   @NotNull
-  public static PydevConsoleRunner runPythonConsole(Project project, Module contextModule) {
+  public static PydevConsoleRunner runPythonConsole(Project project, Module contextModule, @Nullable ToolWindow toolWindow) {
     assert project != null : "Project is null";
 
     Pair<Sdk, Module> sdkAndModule = findPythonSdkAndModule(project, contextModule);
@@ -152,7 +154,7 @@
     envs.put(PythonEnvUtil.IPYTHONENABLE, ipythonEnabled);
 
     return PydevConsoleRunner
-      .createAndRun(project, sdk, PyConsoleType.PYTHON, workingDir, envs, setupFragment);
+      .createAndRun(project, sdk, PyConsoleType.PYTHON, workingDir, envs, toolWindow, setupFragment);
   }
 
   public static PathMappingSettings getMappings(Project project, Sdk sdk) {
diff --git a/python/src/com/jetbrains/python/documentation/EpydocString.java b/python/src/com/jetbrains/python/documentation/EpydocString.java
index 132ef49..3d0fc3e 100644
--- a/python/src/com/jetbrains/python/documentation/EpydocString.java
+++ b/python/src/com/jetbrains/python/documentation/EpydocString.java
@@ -44,6 +44,13 @@
     "precondition", "postcondition", "invariant", "author", "organization", "copyright", "license", "contact", "summary", "see"
   };
 
+  /**
+   * Empty doc (for {@link #createParameterType(String, String)} probably)
+   */
+  public EpydocString() {
+    this("");
+  }
+
   public EpydocString(@NotNull String docstringText) {
     super(docstringText, "@");
   }
diff --git a/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java b/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java
index d09af58..c050690 100644
--- a/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java
+++ b/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java
@@ -87,8 +87,6 @@
     for (PyParameter functionParam : function.getParameterList().getParameters()) {
       String paramName = functionParam.getName();
       if (!functionParam.isSelf() && !StringUtil.isEmpty(paramName)) {
-        assert paramName != null;
-
         String type = signature != null ? signature.getArgTypeQualifiedName(paramName) : null;
 
         if (type != null) {
@@ -140,12 +138,9 @@
 
       final VirtualFile virtualFile = myFile.getVirtualFile();
       if (virtualFile == null) return;
-      OpenFileDescriptor descriptor = new OpenFileDescriptor(
-        myProject, virtualFile, myDocStringOwner.getTextOffset() + myDocStringOwner.getTextLength()
-      );
+      OpenFileDescriptor descriptor = new OpenFileDescriptor(myProject, virtualFile, myDocStringExpression.getTextOffset());
       Editor targetEditor = FileEditorManager.getInstance(myProject).openTextEditor(descriptor, true);
       if (targetEditor != null) {
-        targetEditor.getCaretModel().moveToOffset(myDocStringExpression.getTextOffset());
         TemplateManager.getInstance(myProject).startTemplate(targetEditor, template);
       }
     }
@@ -298,7 +293,7 @@
     if (myDocStringOwner instanceof PyFunction) {
       final PyStatementList statementList = ((PyFunction)myDocStringOwner).getStatementList();
       final Document document = PsiDocumentManager.getInstance(myProject).getDocument(getFile());
-      if (document != null && statementList != null && myFunction != null && statementList.getStatements().length != 0
+      if (document != null && myFunction != null && statementList.getStatements().length != 0
           && document.getLineNumber(statementList.getTextOffset()) != document.getLineNumber(myFunction.getTextOffset())) {
         whitespace = PsiTreeUtil.getPrevSiblingOfType(statementList, PsiWhiteSpace.class);
       }
@@ -411,7 +406,7 @@
       final PyStatementList list = myFunction.getStatementList();
       final Document document = PsiDocumentManager.getInstance(myProject).getDocument(getFile());
 
-      if (document != null && list != null) {
+      if (document != null) {
         if (document.getLineNumber(list.getTextOffset()) == document.getLineNumber(myFunction.getTextOffset()) ||
             list.getStatements().length == 0) {
           PyFunction func = elementGenerator.createFromText(LanguageLevel.forElement(myFunction),
diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java
index 6ea6540..49a17ef 100644
--- a/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java
+++ b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java
@@ -15,7 +15,9 @@
  */
 package com.jetbrains.python.documentation;
 
-import com.intellij.openapi.components.*;
+import com.intellij.openapi.components.PersistentStateComponent;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleServiceManager;
 import com.intellij.openapi.util.text.StringUtil;
@@ -27,6 +29,7 @@
 import com.jetbrains.python.psi.PyTargetExpression;
 import com.jetbrains.python.psi.impl.PyPsiUtils;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.List;
 
@@ -54,7 +57,7 @@
 
   private boolean isFormat(PsiFile file, final String format) {
     if (file instanceof PyFile) {
-      PyTargetExpression expr = ((PyFile) file).findTopLevelAttribute(PyNames.DOCFORMAT);
+      PyTargetExpression expr = ((PyFile)file).findTopLevelAttribute(PyNames.DOCFORMAT);
       if (expr != null) {
         String docformat = PyPsiUtils.strValue(expr.findAssignedValue());
         if (docformat != null) {
@@ -88,4 +91,21 @@
   public void loadState(PyDocumentationSettings state) {
     XmlSerializerUtil.copyBean(state, this);
   }
+
+  /**
+   * TODO: Use this factory for the whole document infrastructure to simplify new documentation engine support
+   * Factory that returns appropriate instance of {@link StructuredDocStringBase} if specificed
+   *
+   * @return instance or null if no doctype os set
+   */
+  @Nullable
+  public StructuredDocStringBase getDocString() {
+    if (myDocStringFormat.equals(DocStringFormat.EPYTEXT)) {
+      return new EpydocString();
+    }
+    if (myDocStringFormat.equals(DocStringFormat.REST)) {
+      return new SphinxDocString();
+    }
+    return null;
+  }
 }
diff --git a/python/src/com/jetbrains/python/documentation/SphinxDocString.java b/python/src/com/jetbrains/python/documentation/SphinxDocString.java
index ae5c705..c04e1c9 100644
--- a/python/src/com/jetbrains/python/documentation/SphinxDocString.java
+++ b/python/src/com/jetbrains/python/documentation/SphinxDocString.java
@@ -31,7 +31,14 @@
                                                    ":type", ":raise", ":raises", ":var", ":cvar", ":ivar",
                                                    ":return", ":returns", ":rtype", ":except", ":exception" };
 
-  public SphinxDocString(@NotNull String docstringText) {
+  /**
+   * Empty doc (for {@link #createParameterType(String, String)} probably)
+   */
+  public SphinxDocString() {
+    this("");
+  }
+
+  public SphinxDocString(@NotNull final String docstringText) {
     super(docstringText, ":");
   }
 
diff --git a/python/src/com/jetbrains/python/documentation/StructuredDocStringBase.java b/python/src/com/jetbrains/python/documentation/StructuredDocStringBase.java
index 9f6cecb..36b70ec 100644
--- a/python/src/com/jetbrains/python/documentation/StructuredDocStringBase.java
+++ b/python/src/com/jetbrains/python/documentation/StructuredDocStringBase.java
@@ -43,18 +43,21 @@
   private static final Pattern RE_LOOSE_TAG_LINE = Pattern.compile("([a-z]+) ([a-zA-Z_0-9]*):?([^:]*)");
   private static final Pattern RE_ARG_TYPE = Pattern.compile("(.*) ([a-zA-Z_0-9]+)");
 
-  public static String[] PARAM_TAGS = new String[] { "param", "parameter", "arg", "argument" };
-  public static String[] PARAM_TYPE_TAGS = new String[] { "type" };
-  public static String[] VARIABLE_TAGS = new String[] { "ivar", "cvar", "var" };
+  public static String[] PARAM_TAGS = new String[]{"param", "parameter", "arg", "argument"};
+  public static String[] PARAM_TYPE_TAGS = new String[]{"type"};
+  public static String[] VARIABLE_TAGS = new String[]{"ivar", "cvar", "var"};
 
-  public static String[] RAISES_TAGS = new String[] { "raises", "raise", "except", "exception" };
-  public static String[] RETURN_TAGS = new String[] { "return", "returns" };
+  public static String[] RAISES_TAGS = new String[]{"raises", "raise", "except", "exception"};
+  public static String[] RETURN_TAGS = new String[]{"return", "returns"};
+  @NotNull
+  private final String myTagPrefix;
 
   public enum ReferenceType {PARAMETER, PARAMETER_TYPE, KEYWORD, VARIABLE, CLASS_VARIABLE, INSTANCE_VARIABLE}
 
   public static String TYPE = "type";
 
   protected StructuredDocStringBase(@NotNull String docStringText, String tagPrefix) {
+    myTagPrefix = tagPrefix;
     final Substring docString = new Substring(docStringText);
     final List<Substring> lines = docString.splitLines();
     final int nlines = lines.size();
@@ -74,6 +77,12 @@
   }
 
   @Override
+  @NotNull
+  public String createParameterType(@NotNull final String name, @NotNull final String type) {
+    return myTagPrefix + TYPE + String.format(" %s %s", name, type);
+  }
+
+  @Override
   public String getDescription() {
     return myDescription;
   }
@@ -82,8 +91,9 @@
   public String getSummary() {
     final List<String> strings = StringUtil.split(StringUtil.trimLeading(myDescription), "\n", true, false);
     if (strings.size() > 1) {
-      if (strings.get(1).isEmpty())
+      if (strings.get(1).isEmpty()) {
         return strings.get(0);
+      }
     }
     return "";
   }
@@ -216,8 +226,8 @@
 
   @Override
   @Nullable
-  public Substring getParamByNameAndKind(@NotNull String name, String kind)  {
-    for (Substring s: getTagArguments(kind)) {
+  public Substring getParamByNameAndKind(@NotNull String name, String kind) {
+    for (Substring s : getTagArguments(kind)) {
       if (name.equals(s.getValue())) {
         return s;
       }
diff --git a/python/src/com/jetbrains/python/inspections/PyInspectionVisitor.java b/python/src/com/jetbrains/python/inspections/PyInspectionVisitor.java
index 4584f06..6c86e72 100644
--- a/python/src/com/jetbrains/python/inspections/PyInspectionVisitor.java
+++ b/python/src/com/jetbrains/python/inspections/PyInspectionVisitor.java
@@ -78,12 +78,12 @@
 
   protected final void registerProblem(@Nullable final PsiElement element,
                                        @NotNull final String message,
-                                       @NotNull final LocalQuickFix quickFix) {
+                                       @NotNull final LocalQuickFix... quickFixes) {
     if (element == null || element.getTextLength() == 0) {
       return;
     }
     if (myHolder != null) {
-      myHolder.registerProblem(element, message, quickFix);
+      myHolder.registerProblem(element, message, quickFixes);
     }
   }
 
diff --git a/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java b/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java
index 8b6143f..b2ad485 100644
--- a/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyShadowingNamesInspection.java
@@ -99,7 +99,7 @@
           final ScopeOwner nextOwner = ScopeUtil.getScopeOwner(owner);
           if (nextOwner != null) {
             final ResolveProcessor processor = new ResolveProcessor(name);
-            PyResolveUtil.scopeCrawlUp(processor, nextOwner, null, name, null);
+            PyResolveUtil.scopeCrawlUp(processor, nextOwner, null, name, null, null);
             final PsiElement resolved = processor.getResult();
             if (resolved != null) {
               final PyComprehensionElement comprehension = PsiTreeUtil.getParentOfType(resolved, PyComprehensionElement.class);
diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveArgumentQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveArgumentQuickFix.java
index 0ae7ac1..708e917 100644
--- a/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveArgumentQuickFix.java
+++ b/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveArgumentQuickFix.java
@@ -27,6 +27,7 @@
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 
+//TODO: Remove pydoc aswell
 public class PyRemoveArgumentQuickFix implements LocalQuickFix {
 
   @NotNull
diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java
index 877da8d..cd4e582 100644
--- a/python/src/com/jetbrains/python/psi/PyUtil.java
+++ b/python/src/com/jetbrains/python/psi/PyUtil.java
@@ -901,6 +901,20 @@
   }
 
   /**
+   * Returns first child psi element with specified element type or {@code null} if no such element exists.
+   * Semantically it's the same as {@code getChildByFilter(element, TokenSet.create(type), 0)}.
+   *
+   * @param element tree parent node
+   * @param type    element type expected
+   * @return child element described
+   */
+  @Nullable
+  public static PsiElement getFirstChildOfType(@NotNull final PsiElement element, @NotNull PyElementType type) {
+    final ASTNode child = element.getNode().findChildByType(type);
+    return child != null ? child.getPsi() : null;
+  }
+
+  /**
    * If argument is a PsiDirectory, turn it into a PsiFile that points to __init__.py in that directory.
    * If there's no __init__.py there, null is returned, there's no point to resolve to a dir which is not a package.
    * Alas, resolve() and multiResolve() can't return anything but a PyFile or PsiFileImpl.isPsiUpToDate() would fail.
@@ -1645,6 +1659,11 @@
     return Collections2.filter(pyMemberInfos, new ObjectPredicate(false));
   }
 
+  public static boolean isStarImportableFrom(@NotNull String name, @NotNull PyFile file) {
+    final List<String> dunderAll = file.getDunderAll();
+    return dunderAll != null ? dunderAll.contains(name) : !name.startsWith("_");
+  }
+
   /**
    * Filters only pyclass object (new class)
    */
diff --git a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java
index 744dc97..bb0e29f 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java
@@ -24,6 +24,7 @@
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiFileFactory;
+import com.intellij.psi.PsiWhiteSpace;
 import com.intellij.psi.impl.PsiFileFactoryImpl;
 import com.intellij.psi.impl.source.tree.LeafPsiElement;
 import com.intellij.psi.tree.TokenSet;
@@ -86,8 +87,9 @@
   }
 
 
+  @Override
   public PyStringLiteralExpression createStringLiteralFromString(@NotNull String unescaped) {
-    return createStringLiteralFromString(null, unescaped);
+    return createStringLiteralFromString(null, unescaped, true);
   }
 
   public PyStringLiteralExpression createStringLiteral(@NotNull PyStringLiteralExpression oldElement, @NotNull String unescaped) {
@@ -100,7 +102,11 @@
     }
   }
 
-  public PyStringLiteralExpression createStringLiteralFromString(@Nullable PsiFile destination, @NotNull String unescaped) {
+
+  @Override
+  public PyStringLiteralExpression createStringLiteralFromString(@Nullable PsiFile destination,
+                                                                 @NotNull String unescaped,
+                                                                 final boolean preferUTF8) {
     boolean useDouble = !unescaped.contains("\"");
     boolean useMulti = unescaped.matches(".*(\r|\n).*");
     String quotes;
@@ -115,7 +121,7 @@
     VirtualFile vfile = destination == null ? null : destination.getVirtualFile();
     Charset charset;
     if (vfile == null) {
-      charset = Charset.forName("US-ASCII");
+      charset = (preferUTF8 ? Charset.forName("UTF-8") : Charset.forName("US-ASCII"));
     }
     else {
       charset = vfile.getCharset();
@@ -191,7 +197,7 @@
     final LeafPsiElement[] leafs = PsiTreeUtil.getChildrenOfType(list, LeafPsiElement.class);
     if (leafs != null) {
       final Deque<LeafPsiElement> commas = Queues.newArrayDeque(Collections2.filter(Arrays.asList(leafs), COMMAS_ONLY));
-      if (! commas.isEmpty()) {
+      if (!commas.isEmpty()) {
         final LeafPsiElement lastComma = commas.getLast();
         if (PsiTreeUtil.getNextSiblingOfType(lastComma, PyExpression.class) == null) { //Comma has no expression after it
           lastComma.delete();
@@ -297,7 +303,7 @@
                                    AccessDirection accessDirection) {
     String propertyText;
     if (accessDirection == AccessDirection.DELETE) {
-      propertyText = "@" + propertyName +".deleter\ndef " + propertyName + "(self):\n  del self." + fieldName;
+      propertyText = "@" + propertyName + ".deleter\ndef " + propertyName + "(self):\n  del self." + fieldName;
     }
     else if (accessDirection == AccessDirection.WRITE) {
       propertyText = "@" + propertyName + ".setter\ndef " + propertyName + "(self, value):\n  self." + fieldName + " = value";
@@ -415,6 +421,12 @@
                           PyExpressionStatement.class, content + "\n");
   }
 
+  @NotNull
+  @Override
+  public PsiElement createNewLine() {
+    return createFromText(LanguageLevel.getDefault(), PsiWhiteSpace.class, " \n\n ");
+  }
+
   private static class CommasOnly extends NotNullPredicate<LeafPsiElement> {
     @Override
     protected boolean applyNotNull(@NotNull final LeafPsiElement input) {
diff --git a/python/src/com/jetbrains/python/psi/impl/PyElsePartImpl.java b/python/src/com/jetbrains/python/psi/impl/PyElsePartImpl.java
index ddd02d5..8c69893 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyElsePartImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyElsePartImpl.java
@@ -15,26 +15,16 @@
  */
 package com.jetbrains.python.psi.impl;
 
-import com.jetbrains.python.psi.PyElsePart;
-import com.jetbrains.python.psi.PyStatementList;
-import com.jetbrains.python.PyElementTypes;
 import com.intellij.lang.ASTNode;
+import com.jetbrains.python.psi.PyElsePart;
 
 /**
  * User: dcheryasov
  * Date: Mar 15, 2009 9:40:35 PM
  */
-public class PyElsePartImpl extends PyElementImpl implements PyElsePart {
+public class PyElsePartImpl extends PyStatementPartImpl implements PyElsePart {
   
   public PyElsePartImpl(ASTNode astNode) {
     super(astNode);
   }
-
-  public PyStatementList getStatementList() {
-    ASTNode n = getNode().findChildByType(PyElementTypes.STATEMENT_LISTS);
-    if (n != null) {
-      return (PyStatementList)n.getPsi();
-    }
-    return null;
-  }
 }
diff --git a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java
index 030fcfe..02b7906 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java
@@ -477,8 +477,9 @@
     if (starImportSource != null) {
       starImportSource = PyUtil.turnDirIntoInit(starImportSource);
       if (starImportSource instanceof PyFile) {
-        final PsiElement result = ((PyFile)starImportSource).getElementNamed(name);
-        if (result != null) {
+        final PyFile file = (PyFile)starImportSource;
+        final PsiElement result = file.getElementNamed(name);
+        if (result != null && PyUtil.isStarImportableFrom(name, file)) {
           return result;
         }
       }
diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java
index e0cdf87..9d2be0f 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java
@@ -23,6 +23,7 @@
 import com.intellij.util.ArrayUtil;
 import com.jetbrains.python.PyNames;
 import com.jetbrains.python.PythonFileType;
+import com.jetbrains.python.documentation.StructuredDocStringBase;
 import com.jetbrains.python.psi.*;
 import org.jetbrains.annotations.NotNull;
 
@@ -82,14 +83,21 @@
   /**
    * Adds docstring to function. Provide doc with out of comment blocks.
    *
+   *
    * @param docString doc
    */
   public void docString(@NotNull final String docString) {
-    myDocStringLines = StringUtil.splitByLines(removeIndent(docString));
+    final String[] stringsToAdd = StringUtil.splitByLines(removeIndent(docString));
+    if (myDocStringLines == null) {
+      myDocStringLines = stringsToAdd;
+    }
+    else {
+      myDocStringLines = ArrayUtil.mergeArrays(myDocStringLines, stringsToAdd);
+    }
   }
 
   @NotNull
-  private String removeIndent(@NotNull final String string) {
+  private static String removeIndent(@NotNull final String string) {
     return INDENT_REMOVE_PATTERN.matcher(string).replaceAll("");
   }
 
@@ -97,6 +105,21 @@
     myName = name;
   }
 
+  /**
+   * Adds param and its type to doc
+   * @param name param name
+   * @param type param type
+   * @param docStyle what docstyle to use to doc param type
+   */
+  @NotNull
+  public PyFunctionBuilder parameterWithType(@NotNull final String name,
+                                             @NotNull final String type,
+                                             @NotNull final StructuredDocStringBase docStyle) {
+    parameter(name);
+    docString(docStyle.createParameterType(name, type));
+    return this;
+  }
+
   public PyFunctionBuilder parameter(String baseName) {
     String name = baseName;
     int uniqueIndex = 0;
@@ -173,8 +196,9 @@
 
   /**
    * Adds decorator with argument
+   *
    * @param decoratorName decorator name
-   * @param value its argument
+   * @param value         its argument
    */
   public void decorate(@NotNull final String decoratorName, @NotNull final String value) {
     decorate(decoratorName);
diff --git a/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java
index 4011c23..88c3134 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyStarImportElementImpl.java
@@ -52,11 +52,8 @@
       for (PsiElement importedFile : new HashSet<PsiElement>(importedFiles)) { // resolver gives lots of duplicates
         final PsiElement source = PyUtil.turnDirIntoInit(importedFile);
         if (source instanceof PyFile) {
-          Iterable<PyElement> declaredNames = ((PyFile)source).iterateNames();
-          if (((PyFile)source).getDunderAll() == null) {
-            declaredNames = excludeUnderscoredNames(declaredNames);
-          }
-          chain.add(declaredNames);
+          final PyFile sourceFile = (PyFile)source;
+          chain.add(filterStarImportableNames(sourceFile.iterateNames(), sourceFile));
         }
       }
       return chain;
@@ -64,15 +61,13 @@
     return Collections.emptyList();
   }
 
-  private static Iterable<PyElement> excludeUnderscoredNames(Iterable<PyElement> declaredNames) {
+  @NotNull
+  private static Iterable<PyElement> filterStarImportableNames(@NotNull Iterable<PyElement> declaredNames, @NotNull final PyFile file) {
     return Iterables.filter(declaredNames, new Predicate<PyElement>() {
       @Override
       public boolean apply(@Nullable PyElement input) {
         final String name = input != null ? input.getName() : null;
-        if (name != null && name.startsWith("_")) {
-          return false;
-        }
-        return true;
+        return name != null && PyUtil.isStarImportableFrom(name, file);
       }
     });
   }
@@ -93,11 +88,7 @@
           final List<? extends RatedResolveResult> results = moduleType.resolveMember(name, null, AccessDirection.READ,
                                                                                       PyResolveContext.defaultContext());
           final PsiElement result = results != null && !results.isEmpty() ? results.get(0).getElement() : null;
-          if (result != null) {
-            final List<String> all = sourceFile.getDunderAll();
-            if (all != null ? !all.contains(name) : name.startsWith("_")) {
-              continue;
-            }
+          if (result != null && PyUtil.isStarImportableFrom(name, sourceFile) ) {
             return result;
           }
         }
diff --git a/python/src/com/jetbrains/python/psi/impl/PyStatementPartImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStatementPartImpl.java
index 46e9a78..7454d67 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyStatementPartImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyStatementPartImpl.java
@@ -15,10 +15,11 @@
  */
 package com.jetbrains.python.psi.impl;
 
-import com.jetbrains.python.psi.PyStatementPart;
-import com.jetbrains.python.psi.PyStatementList;
-import com.jetbrains.python.PyElementTypes;
 import com.intellij.lang.ASTNode;
+import com.jetbrains.python.PyElementTypes;
+import com.jetbrains.python.psi.PyStatementList;
+import com.jetbrains.python.psi.PyStatementPart;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Abstract statement part implementation; extracts the statements list.
@@ -30,11 +31,8 @@
     super(astNode);
   }
 
+  @NotNull
   public PyStatementList getStatementList() {
-    ASTNode n = getNode().findChildByType(PyElementTypes.STATEMENT_LISTS);
-    if (n != null) {
-      return (PyStatementList)n.getPsi();
-    }
-    return null;
+    return childToPsiNotNull(PyElementTypes.STATEMENT_LIST);
   }
 }
diff --git a/python/src/com/jetbrains/python/psi/impl/PyWithStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyWithStatementImpl.java
index 98cd8e5..3af3771 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyWithStatementImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyWithStatementImpl.java
@@ -63,6 +63,14 @@
   }
 
   public PyWithItem[] getWithItems() {
-    return childrenToPsi(WITH_ITEM, PyWithItem.EMPTY_ARRAY); 
+    return childrenToPsi(WITH_ITEM, PyWithItem.EMPTY_ARRAY);
+  }
+
+  @Override
+  @NotNull
+  public PyStatementList getStatementList() {
+    final PyStatementList statementList = childToPsi(PyElementTypes.STATEMENT_LIST);
+    assert statementList != null : "Statement list missing for with statement " + getText();
+    return statementList;
   }
 }
diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java
index da8c0d6..838a4db 100644
--- a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java
+++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java
@@ -38,6 +38,11 @@
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
 /**
  * Ref resolution routines.
  * User: dcheryasov
@@ -106,16 +111,17 @@
         owner = outerScopeOwner;
       }
     }
-    scopeCrawlUp(processor, owner, originalOwner, name, roof);
+    scopeCrawlUp(processor, owner, originalOwner, name, roof, realContext);
   }
 
   public static void scopeCrawlUp(@NotNull PsiScopeProcessor processor, @NotNull ScopeOwner scopeOwner, @Nullable String name,
                                   @Nullable PsiElement roof) {
-    scopeCrawlUp(processor, scopeOwner, scopeOwner, name, roof);
+    scopeCrawlUp(processor, scopeOwner, scopeOwner, name, roof, null);
   }
 
   public static void scopeCrawlUp(@NotNull PsiScopeProcessor processor, @Nullable ScopeOwner scopeOwner,
-                                  @Nullable ScopeOwner originalScopeOwner, @Nullable String name, @Nullable PsiElement roof) {
+                                  @Nullable ScopeOwner originalScopeOwner, @Nullable String name, @Nullable PsiElement roof,
+                                  @Nullable final PsiElement anchor) {
     while (scopeOwner != null) {
       if (!(scopeOwner instanceof PyClass) || scopeOwner == originalScopeOwner) {
         final Scope scope = ControlFlowCache.getScope(scopeOwner);
@@ -136,7 +142,31 @@
             }
           }
         }
-        for (NameDefiner definer : scope.getImportedNameDefiners()) {
+        List<NameDefiner> definers = new ArrayList<NameDefiner>(scope.getImportedNameDefiners());
+        if (anchor != null && ScopeUtil.getScopeOwner(anchor) == scopeOwner) {
+          final Comparator<NameDefiner> nearestDefinerComparator = new Comparator<NameDefiner>() {
+            @Override
+            public int compare(NameDefiner a, NameDefiner b) {
+              final boolean aIsBefore = PyPsiUtils.isBefore(a, anchor);
+              final boolean bIsBefore = PyPsiUtils.isBefore(b, anchor);
+              final int diff = a.getTextOffset() - b.getTextOffset();
+              if (aIsBefore && bIsBefore) {
+                return -diff;
+              }
+              else if (aIsBefore) {
+                return -1;
+              }
+              else if (bIsBefore) {
+                return 1;
+              }
+              else {
+                return diff;
+              }
+            }
+          };
+          Collections.sort(definers, nearestDefinerComparator);
+        }
+        for (NameDefiner definer : definers) {
           if (!processor.execute(definer, ResolveState.initial())) {
             found = true;
             break;
diff --git a/python/src/com/jetbrains/python/run/PyRemoteTracebackFilter.java b/python/src/com/jetbrains/python/run/PyRemoteTracebackFilter.java
new file mode 100644
index 0000000..2af1a64
--- /dev/null
+++ b/python/src/com/jetbrains/python/run/PyRemoteTracebackFilter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.run;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.remote.RemoteProcessHandlerBase;
+import com.intellij.util.PathMappingSettings;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author traff
+ */
+public class PyRemoteTracebackFilter extends PythonTracebackFilter {
+  private final RemoteProcessHandlerBase myHandler;
+
+  public PyRemoteTracebackFilter(Project project, String workingDirectory, RemoteProcessHandlerBase remoteProcessHandler) {
+    super(project, workingDirectory);
+
+    myHandler = remoteProcessHandler;
+  }
+
+  @Override
+  @Nullable
+  protected VirtualFile findFileByName(String fileName) {
+    VirtualFile vFile = super.findFileByName(fileName);
+    if (vFile != null) {
+      return vFile;
+    }
+    for (PathMappingSettings.PathMapping m : myHandler.getMappingSettings().getPathMappings()) {
+      if (m.canReplaceRemote(fileName)) {
+        VirtualFile file = LocalFileSystem.getInstance().findFileByPath(m.mapToLocal(fileName));
+        if (file != null && file.exists()) {
+          return file;
+        }
+      }
+    }
+
+
+
+    return null;
+  }
+}
diff --git a/python/src/com/jetbrains/python/run/PythonCommandLineState.java b/python/src/com/jetbrains/python/run/PythonCommandLineState.java
index 216e68c..a4d1718 100644
--- a/python/src/com/jetbrains/python/run/PythonCommandLineState.java
+++ b/python/src/com/jetbrains/python/run/PythonCommandLineState.java
@@ -47,6 +47,7 @@
 import com.intellij.openapi.util.registry.Registry;
 import com.intellij.openapi.vfs.JarFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.remote.RemoteProcessHandlerBase;
 import com.intellij.util.PlatformUtils;
 import com.intellij.util.containers.HashMap;
 import com.jetbrains.python.PythonHelpersLocator;
@@ -80,7 +81,9 @@
   public static final String GROUP_DEBUGGER = "Debugger";
   public static final String GROUP_SCRIPT = "Script";
   private final AbstractPythonRunConfiguration myConfig;
-  private final List<Filter> myFilters;
+
+  private final List<Filter> myFilters = Lists.<Filter>newArrayList(new UrlFilter());
+
   private Boolean myMultiprocessDebug = null;
 
   public boolean isDebug() {
@@ -99,15 +102,9 @@
     return serverSocket;
   }
 
-  public PythonCommandLineState(AbstractPythonRunConfiguration runConfiguration, ExecutionEnvironment env, List<Filter> filters) {
+  public PythonCommandLineState(AbstractPythonRunConfiguration runConfiguration, ExecutionEnvironment env) {
     super(env);
     myConfig = runConfiguration;
-    myFilters = Lists.newArrayList(filters);
-    addDefaultFilters();
-  }
-
-  protected void addDefaultFilters() {
-    myFilters.add(new UrlFilter());
   }
 
   @Nullable
@@ -134,10 +131,23 @@
   protected ConsoleView createAndAttachConsole(Project project, ProcessHandler processHandler, Executor executor)
     throws ExecutionException {
     final ConsoleView consoleView = createConsoleBuilder(project).filters(myFilters).getConsole();
+
+    addTracebackFilter(project, consoleView, processHandler);
+
     consoleView.attachToProcess(processHandler);
     return consoleView;
   }
 
+  protected void addTracebackFilter(Project project, ConsoleView consoleView, ProcessHandler processHandler) {
+    if (PySdkUtil.isRemote(myConfig.getSdk())) {
+      assert processHandler instanceof RemoteProcessHandlerBase;
+      consoleView.addMessageFilter(new PyRemoteTracebackFilter(project, myConfig.getWorkingDirectory(), (RemoteProcessHandlerBase) processHandler));
+    }
+    else {
+      consoleView.addMessageFilter(new PythonTracebackFilter(project, myConfig.getWorkingDirectory()));
+    }
+  }
+
   private TextConsoleBuilder createConsoleBuilder(Project project) {
     if (isDebug()) {
       return new PyDebugConsoleBuilder(project, PythonSdkType.findSdkByPath(myConfig.getInterpreterPath()));
diff --git a/python/src/com/jetbrains/python/run/PythonRunConfiguration.java b/python/src/com/jetbrains/python/run/PythonRunConfiguration.java
index bbba444..118c27b 100644
--- a/python/src/com/jetbrains/python/run/PythonRunConfiguration.java
+++ b/python/src/com/jetbrains/python/run/PythonRunConfiguration.java
@@ -15,11 +15,9 @@
  */
 package com.jetbrains.python.run;
 
-import com.google.common.collect.Lists;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.Executor;
 import com.intellij.execution.configurations.*;
-import com.intellij.execution.filters.Filter;
 import com.intellij.execution.runners.ExecutionEnvironment;
 import com.intellij.openapi.components.PathMacroManager;
 import com.intellij.openapi.options.SettingsEditor;
@@ -40,7 +38,6 @@
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
-import java.util.List;
 
 /**
  * @author yole
@@ -64,10 +61,7 @@
   }
 
   public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException {
-    List<Filter> filters = Lists.newArrayList();
-    filters.add(new PythonTracebackFilter(getProject(), getWorkingDirectory()));
-
-    return new PythonScriptCommandLineState(this, env, filters);
+    return new PythonScriptCommandLineState(this, env);
   }
 
   public void checkConfiguration() throws RuntimeConfigurationException {
diff --git a/python/src/com/jetbrains/python/run/PythonScriptCommandLineState.java b/python/src/com/jetbrains/python/run/PythonScriptCommandLineState.java
index 53ef554..81d2daa 100644
--- a/python/src/com/jetbrains/python/run/PythonScriptCommandLineState.java
+++ b/python/src/com/jetbrains/python/run/PythonScriptCommandLineState.java
@@ -18,20 +18,17 @@
 import com.intellij.execution.configurations.GeneralCommandLine;
 import com.intellij.execution.configurations.ParametersList;
 import com.intellij.execution.configurations.ParamsGroup;
-import com.intellij.execution.filters.Filter;
 import com.intellij.execution.runners.ExecutionEnvironment;
 import com.intellij.openapi.util.text.StringUtil;
 
-import java.util.List;
-
 /**
  * @author yole
  */
 public class PythonScriptCommandLineState extends PythonCommandLineState {
   private final PythonRunConfiguration myConfig;
 
-  public PythonScriptCommandLineState(PythonRunConfiguration runConfiguration, ExecutionEnvironment env, List<Filter> filters) {
-    super(runConfiguration, env, filters);
+  public PythonScriptCommandLineState(PythonRunConfiguration runConfiguration, ExecutionEnvironment env) {
+    super(runConfiguration, env);
     myConfig = runConfiguration;
   }
 
diff --git a/python/src/com/jetbrains/python/run/PythonTracebackFilter.java b/python/src/com/jetbrains/python/run/PythonTracebackFilter.java
index e3bd879..73a3abd 100644
--- a/python/src/com/jetbrains/python/run/PythonTracebackFilter.java
+++ b/python/src/com/jetbrains/python/run/PythonTracebackFilter.java
@@ -51,10 +51,7 @@
     if (matcher.find()) {
       String fileName = matcher.group(1).replace('\\', '/');
       int lineNumber = Integer.parseInt(matcher.group(2));
-      VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fileName);
-      if (vFile == null && !StringUtil.isEmptyOrSpaces(myWorkingDirectory)) {
-        vFile = LocalFileSystem.getInstance().findFileByIoFile(new File(myWorkingDirectory, fileName)); 
-      }
+      VirtualFile vFile = findFileByName(fileName);
       
       if (vFile != null) {
         OpenFileHyperlinkInfo hyperlink = new OpenFileHyperlinkInfo(myProject, vFile, lineNumber - 1);
@@ -66,4 +63,13 @@
     }
     return null;
   }
+
+  @Nullable
+  protected VirtualFile findFileByName(String fileName) {
+    VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fileName);
+    if (vFile == null && !StringUtil.isEmptyOrSpaces(myWorkingDirectory)) {
+      vFile = LocalFileSystem.getInstance().findFileByIoFile(new File(myWorkingDirectory, fileName));
+    }
+    return vFile;
+  }
 }
diff --git a/python/src/com/jetbrains/python/sdk/PyDetectedSdk.java b/python/src/com/jetbrains/python/sdk/PyDetectedSdk.java
index 098462e..f288bd9 100644
--- a/python/src/com/jetbrains/python/sdk/PyDetectedSdk.java
+++ b/python/src/com/jetbrains/python/sdk/PyDetectedSdk.java
@@ -8,4 +8,8 @@
     setHomePath(name);
   }
 
+  @Override
+  public String getVersionString() {
+    return "";
+  }
 }
diff --git a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java
index 39e4e96..1a5aafe 100644
--- a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java
+++ b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java
@@ -22,7 +22,6 @@
 import com.intellij.execution.Executor;
 import com.intellij.execution.configurations.GeneralCommandLine;
 import com.intellij.execution.configurations.ParamsGroup;
-import com.intellij.execution.filters.Filter;
 import com.intellij.execution.process.ProcessHandler;
 import com.intellij.execution.runners.ExecutionEnvironment;
 import com.intellij.execution.testframework.TestFrameworkRunningModel;
@@ -42,12 +41,10 @@
 import com.jetbrains.python.run.AbstractPythonRunConfiguration;
 import com.jetbrains.python.run.CommandLinePatcher;
 import com.jetbrains.python.run.PythonCommandLineState;
-import com.jetbrains.python.run.PythonTracebackFilter;
 import com.jetbrains.python.sdk.PythonSdkType;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -62,7 +59,7 @@
   }
 
   public PythonTestCommandLineStateBase(AbstractPythonRunConfiguration configuration, ExecutionEnvironment env) {
-    super(configuration, env, Collections.<Filter>emptyList());
+    super(configuration, env);
     myConfiguration = configuration;
   }
 
@@ -77,7 +74,6 @@
                                                                                       consoleProperties,
                                                                                       getEnvironment());
       final ConsoleView consoleView = new PythonDebugLanguageConsoleView(project, PythonSdkType.findSdkByPath(myConfiguration.getInterpreterPath()), testsOutputConsoleView);
-      consoleView.addMessageFilter(new PythonTracebackFilter(project, myConfiguration.getWorkingDirectory()));
       consoleView.attachToProcess(processHandler);
       return consoleView;
     }
@@ -85,7 +81,7 @@
                                                                                       processHandler,
                                                                                       consoleProperties,
                                                                                       getEnvironment());
-    consoleView.addMessageFilter(new PythonTracebackFilter(project, myConfiguration.getWorkingDirectory()));
+    addTracebackFilter(project, consoleView, processHandler);
     return consoleView;
   }
 
diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java b/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java
index 3637f37..581e688 100644
--- a/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java
+++ b/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java
@@ -81,7 +81,7 @@
   protected ConsoleView createAndAttachConsole(Project project, ProcessHandler processHandler, Executor executor)
     throws ExecutionException {
     final ConsoleView consoleView = super.createAndAttachConsole(project, processHandler, executor);
-    consoleView.addMessageFilter(new PyTestTracebackFilter(project, myConfiguration.getWorkingDirectory()));
+    addTracebackFilter(project, consoleView, processHandler);
     return consoleView;
   }
 }
diff --git a/python/testData/codeInsight/smartEnter/withExpressionMissing.py b/python/testData/codeInsight/smartEnter/withExpressionMissing.py
new file mode 100644
index 0000000..25526ea
--- /dev/null
+++ b/python/testData/codeInsight/smartEnter/withExpressionMissing.py
@@ -0,0 +1 @@
+with <caret>
\ No newline at end of file
diff --git a/python/testData/codeInsight/smartEnter/withExpressionMissing_after.py b/python/testData/codeInsight/smartEnter/withExpressionMissing_after.py
new file mode 100644
index 0000000..722e64a
--- /dev/null
+++ b/python/testData/codeInsight/smartEnter/withExpressionMissing_after.py
@@ -0,0 +1 @@
+with <caret>:
\ No newline at end of file
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/a.py b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/a.py
new file mode 100644
index 0000000..6d44496
--- /dev/null
+++ b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/a.py
@@ -0,0 +1,3 @@
+import b
+<warning descr="Unused import statement">from p1 import *</warning>
+print(b)
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/b.py b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/b.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/b.py
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/p1/__init__.py b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/p1/__init__.py
new file mode 100644
index 0000000..31f7e1a
--- /dev/null
+++ b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/p1/__init__.py
@@ -0,0 +1 @@
+from p1.m1 import *
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/p1/m1.py b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/p1/m1.py
new file mode 100644
index 0000000..3f8018d
--- /dev/null
+++ b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarDunderAll/p1/m1.py
@@ -0,0 +1,2 @@
+import b
+__all__ = []
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarImport/a.py b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarImport/a.py
new file mode 100644
index 0000000..7495833
--- /dev/null
+++ b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarImport/a.py
@@ -0,0 +1,5 @@
+import m1
+
+print(m1.foo)
+
+<warning descr="Unused import statement">from m2 import *</warning>
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarImport/m1.py b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarImport/m1.py
new file mode 100644
index 0000000..1cfbade
--- /dev/null
+++ b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarImport/m1.py
@@ -0,0 +1 @@
+foo = 0
diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarImport/m2.py b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarImport/m2.py
new file mode 100644
index 0000000..5215a01
--- /dev/null
+++ b/python/testData/inspections/PyUnresolvedReferencesInspection/UnusedImportBeforeStarImport/m2.py
@@ -0,0 +1 @@
+import m1
diff --git a/python/testSrc/com/jetbrains/python/PySmartEnterTest.java b/python/testSrc/com/jetbrains/python/PySmartEnterTest.java
index b8421c9..09e0317 100644
--- a/python/testSrc/com/jetbrains/python/PySmartEnterTest.java
+++ b/python/testSrc/com/jetbrains/python/PySmartEnterTest.java
@@ -179,4 +179,9 @@
   public void testWithTargetIncomplete() {
     doTest();
   }
+
+  // PY-12877
+  public void testWithExpressionMissing() {
+    doTest();
+  }
 }
diff --git a/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java
index 6850c3b..45d582a 100644
--- a/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java
+++ b/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java
@@ -15,6 +15,9 @@
  */
 package com.jetbrains.python.fixtures;
 
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.codeInspection.LocalQuickFix;
+import com.intellij.codeInspection.ex.QuickFixWrapper;
 import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.extensions.Extensions;
 import com.intellij.openapi.module.Module;
@@ -28,6 +31,7 @@
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiReference;
 import com.intellij.testFramework.LightProjectDescriptor;
 import com.intellij.testFramework.PlatformTestCase;
@@ -131,8 +135,32 @@
     }
   }
 
-  protected static void assertNotParsed(PyFile file) {
-    assertNull(PARSED_ERROR_MSG, ((PyFileImpl)file).getTreeElement());
+  /**
+   * Searches for quickfix itetion by its class
+   * @param clazz quick fix class
+   * @param <T> quick fix class
+   * @return quick fix or null if nothing found
+   */
+  @Nullable
+  public <T extends LocalQuickFix> T findQuickFixByClassInIntentions(@NotNull final Class<T> clazz) {
+
+    for (final IntentionAction action : myFixture.getAvailableIntentions()) {
+      if ((action instanceof QuickFixWrapper)) {
+        final QuickFixWrapper quickFixWrapper = (QuickFixWrapper)action;
+        final LocalQuickFix fix = quickFixWrapper.getFix();
+        if (clazz.isInstance(fix)) {
+          @SuppressWarnings("unchecked")
+          final T result = (T)fix;
+          return result;
+        }
+      }
+    }
+    return null;
+  }
+
+
+    protected static void assertNotParsed(PyFile file) {
+      assertNull(PARSED_ERROR_MSG, ((PyFileImpl)file).getTreeElement());
   }
 
   /**
@@ -144,6 +172,18 @@
     return myFixture.findElementByText("class " + name, PyClass.class);
   }
 
+  /**
+   * Finds some text and moves cursor to it (if found)
+   *
+   * @param testToFind text to find
+   * @throws AssertionError if element not found
+   */
+  protected void moveByText(@NotNull final String testToFind) {
+    final PsiElement element = myFixture.findElementByText(testToFind, PsiElement.class);
+    assert element != null : "No element found by text: " + testToFind;
+    myFixture.getEditor().getCaretModel().moveToOffset(element.getTextOffset());
+  }
+
   protected static class PyLightProjectDescriptor implements LightProjectDescriptor {
     private final String myPythonVersion;
 
diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java
index 0c5e87a..a0a2a12 100644
--- a/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java
+++ b/python/testSrc/com/jetbrains/python/inspections/PyUnresolvedReferencesInspectionTest.java
@@ -371,6 +371,16 @@
     doMultiFileTest();
   }
 
+  // PY-11472
+  public void testUnusedImportBeforeStarImport() {
+    doMultiFileTest();
+  }
+
+  // PY-13585
+  public void testUnusedImportBeforeStarDunderAll() {
+    doMultiFileTest();
+  }
+
   @NotNull
   @Override
   protected Class<? extends PyInspection> getInspectionClass() {