diff --git a/python/build/pycharm_build.gant b/python/build/pycharm_build.gant
index 3c668288cd34..e61775a07613 100644
--- a/python/build/pycharm_build.gant
+++ b/python/build/pycharm_build.gant
@@ -1,11 +1,11 @@
+import org.jetbrains.jps.Jps
import org.jetbrains.jps.Module
import static org.jetbrains.jps.idea.IdeaProjectLoader.guessHome
-import org.jetbrains.jps.Jps
includeTargets << new File("${guessHome(this as Script)}/community/build/scripts/utils.gant")
includeTool << Jps
-requireProperty("buildNumber", requireProperty("build.number", "102.SNAPSHOT"))
+requireProperty("buildNumber", requireProperty("build.number", "104.SNAPSHOT"))
setProperty("ch", "$home/community")
setProperty("dryRun", false)
diff --git a/python/helpers/pycharm/django_test_manage.py b/python/helpers/pycharm/django_test_manage.py
index d15732efcbef..c1c2dbbee629 100644
--- a/python/helpers/pycharm/django_test_manage.py
+++ b/python/helpers/pycharm/django_test_manage.py
@@ -1,8 +1,14 @@
#!/usr/bin/env python
from pycharm.fix_getpass import fixGetpass
-from pycharm import django_test_settings
+from pycharm import django_settings
from django.core.management import execute_manager
+import os
+manage_file = os.getenv('PYCHARM_DJANGO_MANAGE_MODULE')
+if not manage_file:
+ manage_file = 'manage'
+
if __name__ == "__main__":
+ __import__(manage_file)
fixGetpass()
execute_manager(django_settings)
diff --git a/python/helpers/pycharm/tcmessages.py b/python/helpers/pycharm/tcmessages.py
index 3d8d5a7d8fbf..f54e235252f0 100644
--- a/python/helpers/pycharm/tcmessages.py
+++ b/python/helpers/pycharm/tcmessages.py
@@ -36,7 +36,11 @@ class TeamcityServiceMessages:
def testIgnored(self, testName, message=''):
self.message('testIgnored', name=testName, message=message)
- def testFailed(self, testName, message='', details=''):
+ def testFailed(self, testName, message='', details='', expected='', actual=''):
+ if expected and actual:
+ self.message('testFailed', type='comparisonFailure', name=testName, message=message,
+ details=details, expected=expected, actual=actual)
+ else:
self.message('testFailed', name=testName, message=message, details=details)
def testError(self, testName, message='', details=''):
diff --git a/python/helpers/pycharm/tcunittest.py b/python/helpers/pycharm/tcunittest.py
index f6e35ee9ea55..055401764e38 100644
--- a/python/helpers/pycharm/tcunittest.py
+++ b/python/helpers/pycharm/tcunittest.py
@@ -17,6 +17,26 @@ class TeamcityTestResult(TestResult):
self.messages = TeamcityServiceMessages(self.output, prepend_linebreak=True)
self.current_suite = None
+ def find_first(self, val):
+ quot = val[0]
+ count = 1
+ quote_ind = val[count:].find(quot)
+ while val[count+quote_ind-1] == "\\":
+ count = count + quote_ind + 1
+ quote_ind = val[count:].find(quot)
+
+ return val[0:quote_ind+count+1]
+
+ def find_second(self, val):
+ quot = val[-1]
+ count = 1
+ quote_ind = val[:len(val)-count-1].rfind(quot)
+ while 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))
@@ -46,10 +66,17 @@ class TeamcityTestResult(TestResult):
def addFailure(self, test, err):
TestResult.addFailure(self, test, err)
+ error_value = err[1][0]
+
+ if error_value.startswith("'") or error_value.startswith('"'):
+ first = self.find_first(err[1][0])
+ second = self.find_second(err[1][0])
+ else:
+ first = second = ""
err = self.formatErr(err)
self.messages.testFailed(self.getTestName(test),
- message='Failure', details=err)
+ message='Failure', details=err, expected=first, actual=second)
def addSkip(self, test, reason):
self.messages.testIgnored(self.getTestName(test), message=reason)
diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml
index fcdf8892823d..73a52fe9d45e 100644
--- a/python/src/META-INF/python-plugin-common.xml
+++ b/python/src/META-INF/python-plugin-common.xml
@@ -217,6 +217,10 @@
+
+
+
+
diff --git a/python/src/com/jetbrains/python/codeInsight/PyCodeInsightSettings.java b/python/src/com/jetbrains/python/codeInsight/PyCodeInsightSettings.java
index 6f2b23ec5021..f13ae4b289f2 100644
--- a/python/src/com/jetbrains/python/codeInsight/PyCodeInsightSettings.java
+++ b/python/src/com/jetbrains/python/codeInsight/PyCodeInsightSettings.java
@@ -33,7 +33,11 @@ public class PyCodeInsightSettings implements PersistentStateComponent usages) {
+ return new PyContainingFileRenamer((PyClass) element, newName);
+ }
+
+ public static class PyContainingFileRenamer extends AutomaticRenamer {
+ private final PyClass myClass;
+
+ public PyContainingFileRenamer(PyClass element, String newName) {
+ myClass = element;
+ myElements.add(element.getContainingFile());
+ suggestAllNames(element.getName(), newName);
+ }
+
+ @Override
+ public String getDialogTitle() {
+ return "Rename Containing File";
+ }
+
+ @Override
+ public String getDialogDescription() {
+ return "Rename containing file with the following name to: ";
+ }
+
+ @Override
+ public String entityName() {
+ return "Containing File";
+ }
+
+ @Override
+ protected String nameToCanonicalName(@NonNls String name, PsiNamedElement element) {
+ return FileUtil.getNameWithoutExtension(name);
+ }
+
+ @Override
+ protected String canonicalNameToName(@NonNls String canonicalName, PsiNamedElement element) {
+ return canonicalName + "." + FileUtil.getExtension(myClass.getContainingFile().getName());
+ }
+
+ @Override
+ public boolean isSelectedByDefault() {
+ return true;
+ }
+ }
+}
diff --git a/python/src/com/jetbrains/python/refactoring/rename/PyInheritorRenameFactory.java b/python/src/com/jetbrains/python/refactoring/rename/PyInheritorRenameFactory.java
new file mode 100644
index 000000000000..84ce9ab3e2f3
--- /dev/null
+++ b/python/src/com/jetbrains/python/refactoring/rename/PyInheritorRenameFactory.java
@@ -0,0 +1,63 @@
+package com.jetbrains.python.refactoring.rename;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.refactoring.rename.naming.AutomaticRenamer;
+import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory;
+import com.intellij.usageView.UsageInfo;
+import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.search.PyClassInheritorsSearch;
+
+import java.util.Collection;
+
+/**
+ * @author yole
+ */
+public class PyInheritorRenameFactory implements AutomaticRenamerFactory {
+ @Override
+ public boolean isApplicable(PsiElement element) {
+ return element instanceof PyClass;
+ }
+
+ @Override
+ public String getOptionName() {
+ return "Rename inheritors";
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return PyCodeInsightSettings.getInstance().RENAME_CLASS_INHERITORS;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ PyCodeInsightSettings.getInstance().RENAME_CLASS_INHERITORS = enabled;
+ }
+
+ @Override
+ public AutomaticRenamer createRenamer(PsiElement element, String newName, Collection usages) {
+ return new PyInheritorRenamer((PyClass) element, newName);
+ }
+
+ public static class PyInheritorRenamer extends AutomaticRenamer {
+ public PyInheritorRenamer(PyClass element, String newName) {
+ myElements.addAll(PyClassInheritorsSearch.search(element, false).findAll());
+ suggestAllNames(element.getName(), newName);
+ }
+
+ @Override
+ public String getDialogTitle() {
+ return "Rename Inheritors";
+ }
+
+ @Override
+ public String getDialogDescription() {
+ return "Rename inheritor classes with the following names to:";
+ }
+
+ @Override
+ public String entityName() {
+ return "Inheritor Class";
+ }
+ }
+}
diff --git a/python/src/com/jetbrains/python/refactoring/rename/PyParametersRenameFactory.java b/python/src/com/jetbrains/python/refactoring/rename/PyParametersRenameFactory.java
new file mode 100644
index 000000000000..e38446a0baa0
--- /dev/null
+++ b/python/src/com/jetbrains/python/refactoring/rename/PyParametersRenameFactory.java
@@ -0,0 +1,91 @@
+package com.jetbrains.python.refactoring.rename;
+
+import com.intellij.openapi.util.Comparing;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.refactoring.rename.naming.AutomaticRenamer;
+import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory;
+import com.intellij.usageView.UsageInfo;
+import com.intellij.util.Processor;
+import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
+import com.jetbrains.python.psi.PyFunction;
+import com.jetbrains.python.psi.PyNamedParameter;
+import com.jetbrains.python.psi.PyParameter;
+import com.jetbrains.python.psi.search.PyOverridingMethodsSearch;
+
+import java.util.Collection;
+
+/**
+ * @author yole
+ */
+public class PyParametersRenameFactory implements AutomaticRenamerFactory {
+ @Override
+ public boolean isApplicable(PsiElement element) {
+ if (element instanceof PyParameter) {
+ PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class);
+ return function != null && function.getContainingClass() != null;
+ }
+ return false;
+ }
+
+ @Override
+ public String getOptionName() {
+ return "Rename parameters in hierarchy";
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return PyCodeInsightSettings.getInstance().RENAME_PARAMETERS_IN_HIERARCHY;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ PyCodeInsightSettings.getInstance().RENAME_PARAMETERS_IN_HIERARCHY = enabled;
+ }
+
+ @Override
+ public AutomaticRenamer createRenamer(PsiElement element, String newName, Collection usages) {
+ return new PyParametersRenamer((PyParameter)element, newName);
+ }
+
+ public static class PyParametersRenamer extends AutomaticRenamer {
+
+ public PyParametersRenamer(final PyParameter element, String newName) {
+ PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class);
+ PyOverridingMethodsSearch.search(function, true).forEach(new Processor() {
+ @Override
+ public boolean process(PyFunction pyFunction) {
+ PyParameter[] parameters = pyFunction.getParameterList().getParameters();
+ for (PyParameter parameter : parameters) {
+ PyNamedParameter named = parameter.getAsNamed();
+ if (named != null && Comparing.equal(named.getName(), element.getName())) {
+ myElements.add(named);
+ }
+ }
+ return true;
+ }
+ });
+ suggestAllNames(element.getName(), newName);
+ }
+
+ @Override
+ public String getDialogTitle() {
+ return "Rename parameters";
+ }
+
+ @Override
+ public String getDialogDescription() {
+ return "Rename parameter in hierarchy to:";
+ }
+
+ @Override
+ public String entityName() {
+ return "Parameter";
+ }
+
+ @Override
+ public boolean isSelectedByDefault() {
+ return true;
+ }
+ }
+}
diff --git a/python/testData/inspections/ReplaceNotEqOperator.py b/python/testData/inspections/ReplaceNotEqOperator.py
index 59b313eb8773..3dc25820ba83 100644
--- a/python/testData/inspections/ReplaceNotEqOperator.py
+++ b/python/testData/inspections/ReplaceNotEqOperator.py
@@ -1 +1 @@
-print(a <> b)
\ No newline at end of file
+print(a <> b)
\ No newline at end of file