diff --git a/python/build/DMG_background.png b/python/build/DMG_background.png
new file mode 100644
index 000000000000..132428137a48
Binary files /dev/null and b/python/build/DMG_background.png differ
diff --git a/python/build/Info.plist b/python/build/Info.plist
index ab30a034e4b1..96b333ef9efd 100644
--- a/python/build/Info.plist
+++ b/python/build/Info.plist
@@ -22,7 +22,7 @@
CFBundleSignature
????
CFBundleGetInfoString
- PyCharm @@version@@, build @@build@@. Copyright JetBrains s.r.o., (c) 2000-2010
+ PyCharm @@version@@, build @@build@@. Copyright JetBrains s.r.o., (c) 2000-@@year@@
CFBundleShortVersionString
@@version@@
CFBundleVersion
diff --git a/python/build/build-plugin.xml b/python/build/build-plugin.xml
index 3dab5067a136..beaa086fc2e1 100644
--- a/python/build/build-plugin.xml
+++ b/python/build/build-plugin.xml
@@ -121,6 +121,7 @@
+
diff --git a/python/build/pycharm.exe4j b/python/build/pycharm.exe4j
index b1e4eed185de..c44b2d0b3375 100644
--- a/python/build/pycharm.exe4j
+++ b/python/build/pycharm.exe4j
@@ -55,7 +55,9 @@
-
+
+
+
diff --git a/python/build/pycharm_build.gant b/python/build/pycharm_build.gant
index a0bcad7452c9..d30c130329ba 100644
--- a/python/build/pycharm_build.gant
+++ b/python/build/pycharm_build.gant
@@ -5,7 +5,7 @@ import org.jetbrains.jps.Jps
includeTargets << new File("${guessHome(this as Script)}/community/build/scripts/utils.gant")
includeTool << Jps
-requireProperty("buildNumber", requireProperty("build.number", "98.SNAPSHOT"))
+requireProperty("buildNumber", requireProperty("build.number", "102.SNAPSHOT"))
setProperty("ch", "$home/community")
setProperty("dryRun", false)
@@ -13,23 +13,23 @@ setProperty("jdk16", guessJdk())
setProperty("vmoptions", "-Xms128m -Xmx512m -XX:MaxPermSize=150m -ea")
setProperty("pluginFilter", [
- "htmltools",
- "JavaScriptLanguage",
- "cvsIntegration", "cvs-core", "cvs-plugin", "javacvs-src", "smartcvs-src",
- "PerforceIntegration", "Perforce", "perforce_util",
- "svn4idea",
- "git4idea",
- "CSS", "css-api",
- "JSInspections",
- "JSIntentionPowerPack",
- "SpellChecker", "spellchecker",
- "sass", "less",
- "hg4idea",
- "tasks", "tasks-core", "tasks-api", "jira-connector",
- "github",
- "python-spellchecker-common", "pycharm-spellchecker",
- "webDeployment"
-])
+ "htmltools",
+ "JavaScriptLanguage",
+ "cvsIntegration", "cvs-core", "cvs-plugin", "javacvs-src", "smartcvs-src",
+ "PerforceIntegration", "Perforce", "perforce_util",
+ "svn4idea",
+ "git4idea",
+ "CSS", "css-api",
+ "JSInspections",
+ "JSIntentionPowerPack",
+ "SpellChecker", "spellchecker",
+ "sass", "less",
+ "hg4idea",
+ "tasks", "tasks-core", "tasks-api", "jira-connector",
+ "github",
+ "python-spellchecker-common", "pycharm-spellchecker",
+ "webDeployment"
+ ])
private List platformImplementationModules() {
return [
@@ -102,27 +102,58 @@ def zipSources() {
ant.mkdir(dir: paths.artifacts)
ant.delete(file: sources)
- ant.zip (destfile: sources) {
- fileset (dir: home) {
+ ant.zip(destfile: sources) {
+ fileset(dir: home) {
["java", "ipr", "iml", "form", "xml", "properties"].each {
- include (name: "**/*.$it")
+ include(name: "**/*.$it")
}
- exclude (name: "**/testData/**")
+ exclude(name: "**/testData/**")
}
}
}
+def buildDebuggerEgg(paths, buildName) {
+ def eggDir = "$paths.sandbox/pycharm-debug"
+ def egg = "$paths.sandbox/pycharm-debug.egg"
+
+ project.stage("building $egg")
+
+ ant.delete(dir: eggDir)
+ ant.mkdir(dir: eggDir)
+ ant.mkdir(dir:"$eggDir/pydev")
+ ant.copy(todir: "$eggDir/pydev") {
+ fileset(dir: "$home/python/helpers/pydev")
+ }
+ ant.mkdir(dir:"$eggDir/EGG-INFO")
+
+ ant.copy(todir: "$eggDir/EGG-INFO") {
+ fileset(dir: "$home/python/resources/debugger-egg/EGG-INFO")
+ }
+
+ ant.replace(file: "$eggDir/EGG-INFO/PKG-INFO") {
+ replacefilter(token: "@@BUILD_NUMBER@@", value: buildName)
+ }
+
+ ant.replace(file: "$eggDir/pydev/pydevd_comm.py") {
+ replacefilter(token: "@@BUILD_NUMBER@@", value: buildName)
+ }
+ ant.zip(destfile: egg) {
+ fileset(dir: eggDir)
+ }
+ ant.copy(file: egg, todir: paths.distAll)
+}
+
target('default': "Build artifacts") {
def modules = [
- "python", "python-ide", "python-pydev", platformApiModules(), platformImplementationModules(), pluginFilter
+ "python", "python-ide", "python-pydev", "colorSchemes", platformApiModules(), platformImplementationModules(), pluginFilter
].flatten()
-
+
loadProject()
// project.dryRun = true
project.stage("Cleaning up sandbox folder")
-
+
project.targetFolder = "$sandbox/classes"
project.dryRun = dryRun
@@ -131,9 +162,8 @@ target('default': "Build artifacts") {
ant.mkdir(dir: sandbox)
}
-
ant.tstamp() {
- format(property: "today.year", pattern: "yyyy")
+ format(property: "todayYear", pattern: "yyyy")
}
ant.xmlproperty(file: "$home/python/resources/idea/PythonApplicationInfo.xml", collapseAttributes: "true")
@@ -201,6 +231,7 @@ target('default': "Build artifacts") {
build_searchable_options()
wire_build_date(buildName)
layoutFull(paths.distAll, usedJars)
+ buildDebuggerEgg(paths, buildName)
scramble()
layoutWin(paths.distWin)
@@ -213,7 +244,7 @@ target('default': "Build artifacts") {
paths.distMac)
buildExe()
-
+
ant.echo(message: buildName, file: "$sandbox/layout/build.txt")
def root = isEap ? "PyCharm-${buildNumber}.app" : "PyCharm ${p("component.version.major")}.${p("component.version.minor")}.app"
@@ -230,8 +261,9 @@ target('default': "Build artifacts") {
}
}
executeExternalAnt("file.name": "pycharm",
+ "dmg.image.name" : "${home}/python/build/DMG_background.png",
"$home/build/buildDmg.xml")
-
+
buildTarGz()
def installer = includeFile("$home/build/scripts/nsis_installer.gant")
@@ -242,7 +274,7 @@ target('default': "Build artifacts") {
}
def build_searchable_options() {
- def dest_dir = "${project ["platform-resources"].output}/search"
+ def dest_dir = "${project["platform-resources"].output}/search"
project.stage("Building searchable options")
ant.mkdir(dir: "${dest_dir}/search")
ant.copy(file: "$home/build/idea.license", todir: paths.ideaSystem)
@@ -346,11 +378,15 @@ def buildTarGz() {
}
tarfileset(dir: "$home/community/bin/linux", prefix: "$tarRoot/bin") {
exclude(name: "libbreakgen*")
+ exclude(name: "fsnotifier*")
+ }
+ tarfileset(dir: "$home/community/bin/linux", filemode: "755", prefix: "$tarRoot/bin") {
+ include(name: "fsnotifier*")
}
tarfileset(dir: "$home/community/bin/nix", filemode: "755", prefix: "$tarRoot/bin") {
include(name: "inspect*.sh")
}
- tarfileset(dir: "$home/python/build", filemode: "755", prefix: "$paths.distUnix/bin") {
+ tarfileset(dir: "$home/python/build", filemode: "755", prefix: "$tarRoot/bin") {
include(name: "pycharm.sh")
}
tarfileset(dir: "$home/python/build", fullpath: "$tarRoot/bin/pycharm.vmoptions") {
@@ -397,6 +433,7 @@ def layoutFull(String target, Set usedJars) {
jar("bootstrap.jar") { module("bootstrap")}
jar("resources.jar") {
module("platform-resources")
+ module("colorSchemes")
}
jar("forms_rt.jar") {
@@ -475,8 +512,8 @@ def layoutFull(String target, Set usedJars) {
}
ant.echo(file: "$target/bin/idea.properties", append: "true", message:
- isEap() ?
-"""
+ isEap() ?
+ """
#-----------------------------------------------------------------------
# Change to 'disabled' if you do not need to receive instant visual
@@ -485,8 +522,8 @@ def layoutFull(String target, Set usedJars) {
idea.fatal.error.notification=enabled
"""
- :
-"""
+ :
+ """
#-----------------------------------------------------------------------
# Change to 'enabled' if you need to receive instant visual notifications about
@@ -528,6 +565,7 @@ def layoutMac(Map args, String target) {
ant.replace(file: "$target/Contents/Info.plist") {
replacefilter(token: "@@build@@", value: args.buildName)
+ replacefilter(token: "@@year@@", value: "$todayYear")
replacefilter(token: "@@version@@", value: args.version)
@@ -548,8 +586,6 @@ def layoutWin(String target) {
fileset(dir: "$home/community/bin/win") {
exclude(name: "breakgen*")
exclude(name: "idea.bat")
- include(name: "runnerw.exe")
-
}
fileset(dir: "$home/python/build") {
include(name: "pycharm.bat")
@@ -557,7 +593,7 @@ def layoutWin(String target) {
}
}
ant.copy(file: "$home/python/help/pycharmhelp.jar", todir: "$target/help")
- ant.echo(file: "$target/bin/pycharm.exe.vmoptions", message: vmoptions.replace(' ', '\n') )
+ ant.echo(file: "$target/bin/pycharm.exe.vmoptions", message: vmoptions.replace(' ', '\n'))
}
def layoutUnix(String target) {
@@ -595,16 +631,16 @@ def executeExternalAnt(Map args, String file) {
classpath {
fileset(dir: "$home/build/lib") {
- include (name: "commons-net-1.4.1.jar")
- include (name: "jsch-0.1.43.jar")
+ include(name: "commons-net-1.4.1.jar")
+ include(name: "jsch-0.1.43.jar")
}
fileset(dir: "$ch/lib/ant/lib") {
- include (name: "*.jar")
+ include(name: "*.jar")
}
- fileset (file: "$jdk16/lib/tools.jar")
- fileset (file: "$ch/lib/oromatcher.jar")
+ fileset(file: "$jdk16/lib/tools.jar")
+ fileset(file: "$ch/lib/oromatcher.jar")
}
arg(value: "-f")
diff --git a/python/helpers/pycharm/docrunner.py b/python/helpers/pycharm/docrunner.py
index e50f3f48ae44..db3ade19b937 100644
--- a/python/helpers/pycharm/docrunner.py
+++ b/python/helpers/pycharm/docrunner.py
@@ -7,6 +7,7 @@ import traceback
import datetime
from tcunittest import TeamcityTestResult
+from tcmessages import TeamcityServiceMessages
class TeamcityDocTestResult(TeamcityTestResult):
"""
@@ -45,7 +46,7 @@ class TeamcityDocTestResult(TeamcityTestResult):
message='Failure', details=err)
def addError(self, test, err = ''):
- self.messages.testFailed(self.getTestName(test),
+ self.messages.testError(self.getTestName(test),
message='Error', details=err)
class DocTestRunner(doctest.DocTestRunner):
@@ -57,6 +58,20 @@ class DocTestRunner(doctest.DocTestRunner):
doctest.DocTestRunner.__init__(self, verbose, optionflags)
self.stream = sys.stdout
self.result = TeamcityDocTestResult(self.stream)
+ self._tests = []
+
+ def addTests(self, tests):
+ self._tests.extend(tests)
+
+ def addTest(self, test):
+ self._tests.append(test)
+
+ def countTests(self):
+ return len(self._tests)
+
+ def start(self):
+ for test in self._tests:
+ self.run(test)
def __run(self, test, compileflags, out):
failures = tries = 0
@@ -152,6 +167,8 @@ modules = {}
from utrunner import debug, getModuleName, PYTHON_VERSION_MAJOR
+runner = DocTestRunner()
+
def loadSource(fileName):
"""
loads source from fileName,
@@ -175,15 +192,13 @@ def testfile(filename):
text, filename = doctest._load_testfile(filename, None, False)
name = os.path.basename(filename)
- globs = {}
- globs['__name__'] = '__main__'
+ globs = {'__name__': '__main__'}
- runner = DocTestRunner()
parser = doctest.DocTestParser()
# Read the file, convert it to a test, and run it.
test = parser.get_doctest(text, globs, name, filename, 0)
if test.examples:
- runner.run(test)
+ runner.addTest(test)
def walkModules(modules, dirname, names):
walkModulesUsingPattern(modules, dirname, names)
@@ -229,7 +244,6 @@ def testFilesInFolderUsingPattern(folder, pattern = ".*"):
return result
if __name__ == "__main__":
- runner = DocTestRunner()
finder = doctest.DocTestFinder()
for arg in sys.argv[1:]:
@@ -265,7 +279,7 @@ if __name__ == "__main__":
tests = finder.find(module, module.__name__)
for test in tests:
if test.examples:
- runner.run(test)
+ runner.addTest(test)
elif len(a) == 2:
# From testcase
@@ -277,8 +291,7 @@ if __name__ == "__main__":
if hasattr(module, a[1]):
testcase = getattr(module, a[1])
tests = finder.find(testcase, testcase.__name__)
- for test in tests:
- runner.run(test)
+ runner.addTests(tests)
else:
raise NameError('Module "%s" has no class "%s"' % (a[0], a[1]))
else:
@@ -293,8 +306,7 @@ if __name__ == "__main__":
if hasattr(module, a[2]):
testcase = getattr(module, a[2])
tests = finder.find(testcase, testcase.__name__)
- for test in tests:
- runner.run(test)
+ runner.addTests(tests)
else:
raise NameError('Module "%s" has no method "%s"' % (a[0], a[2]))
else:
@@ -304,9 +316,12 @@ if __name__ == "__main__":
if hasattr(testCaseClass, a[2]):
testcase = getattr(testCaseClass, a[2])
tests = finder.find(testcase, testcase.__name__)
- for test in tests:
- runner.run(test)
+ runner.addTests(tests)
else:
raise NameError('Class "%s" has no function "%s"' % (testCaseClass, a[2]))
else:
- raise NameError('Module "%s" has no class "%s"' % (module, a[1]))
\ No newline at end of file
+ raise NameError('Module "%s" has no class "%s"' % (module, a[1]))
+
+ debug("/ Loaded " + str(runner.countTests()) + " tests")
+ TeamcityServiceMessages(sys.stdout).testCount(runner.countTests())
+ runner.start()
\ No newline at end of file
diff --git a/python/helpers/pycharm/nose_helper/case.py b/python/helpers/pycharm/nose_helper/case.py
index 842417f715d1..e33314f5ed9e 100644
--- a/python/helpers/pycharm/nose_helper/case.py
+++ b/python/helpers/pycharm/nose_helper/case.py
@@ -87,8 +87,13 @@ class FunctionTestCase(TestBase):
self.suite = TestBase.Suite()
self.suite.__module__ = self.__get_module()
self.suite.__name__ = ""
- self.suite.abs_location = "file://" + imp.find_module(self.suite.__module__)[1]
- self.suite.location = "file://" + imp.find_module(self.suite.__module__)[1]
+ if sys.version.find("IronPython") != -1:
+ # Iron Python doesn't fully support imp
+ self.suite.abs_location = ""
+ self.suite.location = ""
+ else:
+ self.suite.abs_location = "file://" + imp.find_module(self.suite.__module__)[1]
+ self.suite.location = "file://" + imp.find_module(self.suite.__module__)[1]
def _context(self):
return resolve_name(self.test.__module__)
@@ -165,7 +170,12 @@ class MethodTestCase(TestBase):
self.suite = TestBase.Suite()
self.suite.__module__, self.suite.__name__ = self.__get_module()
- self.suite.abs_location = "file://" + imp.find_module(self.suite.__module__)[1]
+
+ if sys.version.find("IronPython") != -1:
+ # Iron Python doesn't fully support imp
+ self.suite.abs_location = ""
+ else:
+ self.suite.abs_location = "file://" + imp.find_module(self.suite.__module__)[1]
self.suite.location = "python_uttestid://" + self.suite.__module__ + "." + self.suite.__name__
def __get_module(self):
diff --git a/python/helpers/pycharm/nose_helper/loader.py b/python/helpers/pycharm/nose_helper/loader.py
index f2bef5dd2893..940fac74aabf 100644
--- a/python/helpers/pycharm/nose_helper/loader.py
+++ b/python/helpers/pycharm/nose_helper/loader.py
@@ -12,7 +12,7 @@ from nose_helper.case import FunctionTestCase, MethodTestCase
from nose_helper.failure import Failure
from nose_helper.config import Config
from nose_helper.selector import defaultSelector
-from nose_helper.util import cmp_lineno, func_lineno, isclass, isgenerator, add_path
+from nose_helper.util import cmp_lineno, func_lineno, isclass, isgenerator, add_path, ismethod, isunboundmethod
from nose_helper.util import transplant_class, transplant_func
from nose_helper.suite import ContextSuiteFactory, ContextList
@@ -22,8 +22,8 @@ op_abspath = os.path.abspath
PYTHON_VERSION_MAJOR = sys.version_info[0]
PYTHON_VERSION_MINOR = sys.version_info[1]
-if PYTHON_VERSION_MAJOR == 3:
- ismethod = isfunction
+#if PYTHON_VERSION_MAJOR == 3:
+# ismethod = isfunction
class TestLoader(unittest.TestLoader):
"""Test loader that extends unittest.TestLoader to support nosetests
@@ -112,6 +112,7 @@ class TestLoader(unittest.TestLoader):
raise
except:
exc = sys.exc_info()
+ print ("1", obj, parent)
return Failure(exc[0], exc[1])
def _makeTest(self, obj, parent=None):
@@ -130,7 +131,7 @@ class TestLoader(unittest.TestLoader):
return self.loadTestsFromTestCase(obj)
else:
return self.loadTestsFromTestClass(obj)
- elif ismethod(obj):
+ elif ismethod(obj) or isunboundmethod(obj):
if parent is None:
parent = obj.__class__
if issubclass(parent, unittest.TestCase):
diff --git a/python/helpers/pycharm/nose_helper/util.py b/python/helpers/pycharm/nose_helper/util.py
index bea6fdd46766..4586ff394f9e 100644
--- a/python/helpers/pycharm/nose_helper/util.py
+++ b/python/helpers/pycharm/nose_helper/util.py
@@ -181,3 +181,53 @@ def make_decorator(func):
newfunc.compat_func_name = name
return newfunc
return decorate
+
+# trick for python 3
+# The following emulates the behavior (we need) of an 'unbound method' under
+# Python 3.x (namely, the ability to have a class associated with a function
+# definition so that things can do stuff based on its associated class)
+
+class UnboundMethod:
+ def __init__(self, cls, func):
+ self.func = func
+ self.__self__ = UnboundSelf(cls)
+
+ def address(self):
+ cls = self.__self__.cls
+ module = cls.__module__
+ m = sys.modules[module]
+ file = getattr(m, '__file__', None)
+ if file is not None:
+ file = os.path.abspath(file)
+ return (nose.util.src(file), module, "%s.%s" % (cls.__name__, self.func.__name__))
+
+ def __call__(self, *args, **kwargs):
+ return self.func(*args, **kwargs)
+
+ def __getattr__(self, attr):
+ return getattr(self.func, attr)
+
+class UnboundSelf:
+ def __init__(self, cls):
+ self.cls = cls
+
+ # We have to do this hackery because Python won't let us override the
+ # __class__ attribute...
+ def __getattribute__(self, attr):
+ if attr == '__class__':
+ return self.cls
+ else:
+ return object.__getattribute__(self, attr)
+
+def unbound_method(cls, func):
+ if inspect.ismethod(func):
+ return func
+ if not inspect.isfunction(func):
+ raise TypeError('%s is not a function' % (repr(func),))
+ return UnboundMethod(cls, func)
+
+def ismethod(obj):
+ return inspect.ismethod(obj) or isinstance(obj, UnboundMethod)
+
+def isunboundmethod(obj):
+ return (inspect.ismethod(obj) and obj.im_self is None) or isinstance(obj, UnboundMethod)
diff --git a/python/helpers/pycharm/nose_utils.py b/python/helpers/pycharm/nose_utils.py
new file mode 100644
index 000000000000..7065412c0cc2
--- /dev/null
+++ b/python/helpers/pycharm/nose_utils.py
@@ -0,0 +1,181 @@
+import unittest
+from tcmessages import TeamcityServiceMessages
+import sys, traceback, datetime
+from tcunittest import strclass, TeamcityTestResult
+
+try:
+ from nose.config import Config
+ from nose.result import TextTestResult
+ from nose.util import isclass # backwards compat
+except:
+ raise NameError("Please, install nosetests")
+
+class TeamcityNoseTestResult(TextTestResult, TeamcityTestResult):
+ """TeamcityTestResult
+ """
+ def __init__(self, stream, descriptions, verbosity, config=None,
+ errorClasses=None):
+ if errorClasses is None:
+ errorClasses = {}
+ self.errorClasses = errorClasses
+ if config is None:
+ config = Config()
+ self.config = config
+ self.output = stream
+ self.messages = TeamcityServiceMessages(self.output, prepend_linebreak=True)
+ self.current_suite = None
+ TextTestResult.__init__(self, stream, descriptions, verbosity, config, errorClasses)
+ TeamcityTestResult.__init__(self, stream)
+
+ def addError(self, test, err):
+ """as in nosetests
+ """
+ ec, ev, tb = err
+ try:
+ exc_info = self._exc_info_to_string(err, test)
+ except TypeError:
+ # 2.3 compat
+ exc_info = self._exc_info_to_string(err)
+ for cls, (storage, label, isfail) in self.errorClasses.items():
+ #if 'Skip' in cls.__name__ or 'Skip' in ec.__name__:
+ # from nose.tools import set_trace
+ # set_trace()
+ if isclass(ec) and issubclass(ec, cls):
+ if isfail:
+ test.passed = False
+ storage.append((test, exc_info))
+ self.printLabel(label, err)
+ return
+ self.errors.append((test, exc_info))
+ test.passed = False
+ self.printLabel('ERROR')
+
+ err = self.formatErr(err)
+
+ self.messages.testError(self.getTestName(test),
+ message='Error', details=err)
+
+ def is_gen(self, test):
+ if hasattr(test.test, "descriptor"):
+ if test.test.descriptor is not None:
+ return True
+ return False
+
+ def getTestName(self, test):
+ test_name_full = str(test)
+ if self.is_gen(test):
+ return test_name_full
+
+ ind_1 = test_name_full.rfind('(')
+ if ind_1 != -1:
+ return test_name_full[:ind_1]
+ ind = test_name_full.rfind('.')
+ if ind != -1:
+ return test_name_full[test_name_full.rfind(".") + 1:]
+ return test_name_full
+
+ def addSuccess(self, test):
+ TextTestResult.addSuccess(self, test)
+
+ def addFailure(self, test, err):
+ TextTestResult.addFailure(self, test, err)
+
+ err = self.formatErr(err)
+
+ self.messages.testFailed(self.getTestName(test),
+ message='Failure', details=err)
+
+ def addSkip(self, test, reason):
+ self.messages.testIgnored(self.getTestName(test), message=reason)
+
+ def __getSuite(self, test):
+ if hasattr(test, "suite"):
+ suite = strclass(test.suite)
+ suite_location = test.suite.location
+ location = test.suite.abs_location
+ if hasattr(test, "lineno"):
+ location = location + ":" + str(test.lineno)
+ else:
+ location = location + ":" + str(test.test.lineno)
+ else:
+ suite = strclass(test.__class__)
+ suite_location = "python_uttestid://" + suite
+ location = "python_uttestid://" + str(test.id())
+ return (location, suite_location)
+
+ def getSuiteName(self, test):
+ test_name_full = str(test)
+
+ if self.is_gen(test):
+ ind_1 = test_name_full.rfind('(')
+ if ind_1 != -1:
+ ind = test_name_full.rfind('.')
+ if ind != -1:
+ return test_name_full[:test_name_full.rfind(".")]
+
+ ind_1 = test_name_full.rfind('(')
+ if ind_1 != -1:
+ return test_name_full[ind_1+1: -1]
+ ind = test_name_full.rfind('.')
+ if ind != -1:
+ return test_name_full[:test_name_full.rfind(".")]
+ return test_name_full
+
+ def startTest(self, test):
+ location, suite_location = self.__getSuite(test)
+ suite = self.getSuiteName(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)
+
+ 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))
+
+ def endLastSuite(self):
+ if self.current_suite:
+ self.messages.testSuiteFinished(self.current_suite)
+ self.current_suite = None
+
+
+class TeamcityNoseRunner(unittest.TextTestRunner):
+ """Test runner that supports teamcity output
+ """
+ def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1,
+ config=None):
+ if config is None:
+ config = Config()
+ self.config = config
+
+ unittest.TextTestRunner.__init__(self, stream, descriptions, verbosity)
+
+
+ def _makeResult(self):
+ return TeamcityNoseTestResult(self.stream,
+ self.descriptions,
+ self.verbosity,
+ self.config)
+
+ def run(self, test):
+ """Overrides to provide plugin hooks and defer all output to
+ the test result class.
+ """
+ wrapper = self.config.plugins.prepareTest(test)
+ if wrapper is not None:
+ test = wrapper
+
+ # plugins can decorate or capture the output stream
+ wrapped = self.config.plugins.setOutputStream(self.stream)
+ if wrapped is not None:
+ self.stream = wrapped
+
+ result = self._makeResult()
+ test(result)
+ result.endLastSuite()
+ return result
diff --git a/python/helpers/pycharm/noserunner.py b/python/helpers/pycharm/noserunner.py
new file mode 100644
index 000000000000..bb193fda382b
--- /dev/null
+++ b/python/helpers/pycharm/noserunner.py
@@ -0,0 +1,65 @@
+import sys
+from utrunner import debug
+from nose_utils import TeamcityNoseRunner
+
+try:
+ from nose.core import TestProgram
+ from nose.plugins.base import Plugin
+ from nose.config import Config
+ from nose.plugins.manager import DefaultPluginManager
+except:
+ raise NameError("Please, install nosetests")
+
+def process_args():
+ tests = []
+
+ if sys.argv != 0:
+ arg = sys.argv[1].strip()
+ if len(arg) == 0:
+ return
+
+ a = arg.split("::")
+ if len(a) == 1:
+ # From module or folder
+ a_splitted = a[0].split(";")
+ if len(a_splitted) != 1:
+ # means we have pattern to match against
+ if a_splitted[0].endswith("/"):
+ debug("/ from folder " + a_splitted[0] + ". Use pattern: " + a_splitted[1])
+ tests.append(a_splitted[0])
+ else:
+ if a[0].endswith("/"):
+ debug("/ from folder " + a[0])
+ tests.append(a[0])
+ else:
+ debug("/ from module " + a[0])
+ tests.append(a[0])
+
+ elif len(a) == 2:
+ # From testcase
+ debug("/ from testcase " + a[1] + " in " + a[0])
+ tests.append(a[0] + ":" + a[1])
+ else:
+ # From method in class or from function
+ debug("/ from method " + a[2] + " in testcase " + a[1] + " in " + a[0])
+ if a[1] == "":
+ # test function, not method
+ tests.append(a[0] + ":" + a[2])
+ else:
+ tests.append(a[0] + ":" + a[1] + "." + a[2])
+
+ argv = ['nosetests']
+
+ if len(sys.argv) > 2:
+ options = sys.argv[2].split()
+ argv.extend(options)
+
+ argv.extend(tests)
+ config = Config(plugins=DefaultPluginManager())
+ config.configure(argv)
+ config.plugins.loadPlugins()
+
+ TestProgram(argv=argv, testRunner=TeamcityNoseRunner(verbosity=config.verbosity, config=config))#, addplugins=[TeamcityPlugin()])
+
+if __name__ == "__main__":
+ process_args()
\ No newline at end of file
diff --git a/python/helpers/pycharm/tcmessages.py b/python/helpers/pycharm/tcmessages.py
index 9bb9ba734973..3d8d5a7d8fbf 100644
--- a/python/helpers/pycharm/tcmessages.py
+++ b/python/helpers/pycharm/tcmessages.py
@@ -38,6 +38,9 @@ class TeamcityServiceMessages:
def testFailed(self, testName, message='', details=''):
self.message('testFailed', name=testName, message=message, details=details)
+
+ def testError(self, testName, message='', details=''):
+ self.message('testFailed', name=testName, message=message, details=details, error="true")
def testStdOut(self, testName, out):
self.message('testStdOut', name=testName, out=out)
diff --git a/python/helpers/pycharm/tcunittest.py b/python/helpers/pycharm/tcunittest.py
index cd96dee62a5a..f6e35ee9ea55 100644
--- a/python/helpers/pycharm/tcunittest.py
+++ b/python/helpers/pycharm/tcunittest.py
@@ -40,7 +40,7 @@ class TeamcityTestResult(TestResult):
err = self.formatErr(err)
- self.messages.testFailed(self.getTestName(test),
+ self.messages.testError(self.getTestName(test),
message='Error', details=err)
def addFailure(self, test, err):
diff --git a/python/helpers/pycharm/utrunner.py b/python/helpers/pycharm/utrunner.py
index 8d7ac8967193..0eee3919c2d8 100644
--- a/python/helpers/pycharm/utrunner.py
+++ b/python/helpers/pycharm/utrunner.py
@@ -1,4 +1,3 @@
-import unittest
import os
import imp
import sys
@@ -7,12 +6,6 @@ import re
from tcmessages import TeamcityServiceMessages
from tcunittest import TeamcityTestRunner
-try:
- import unittest2 as unittest
-except:
- # python version doesn't have unittest2
- pass
-
from nose_helper import TestLoader, ContextSuite
PYTHON_VERSION_MAJOR = sys.version_info[0]
diff --git a/python/helpers/pydev/pydevd.py b/python/helpers/pydev/pydevd.py
index ffcde4a757a4..9fbb2bc40522 100644
--- a/python/helpers/pydev/pydevd.py
+++ b/python/helpers/pydev/pydevd.py
@@ -25,6 +25,7 @@ from pydevd_comm import CMD_CHANGE_VARIABLE, \
CMD_CONSOLE_EXEC, \
CMD_ADD_EXCEPTION_BREAK, \
CMD_REMOVE_EXCEPTION_BREAK, \
+ CMD_LOAD_SOURCE, \
GetGlobalDebugger, \
InternalChangeVariable, \
InternalGetCompletions, \
@@ -203,7 +204,6 @@ class PyDB:
def FinishDebuggingSession(self):
self._finishDebuggingSession = True
-
def acquire(self):
if PyDBUseLocks:
self.lock.acquire()
@@ -278,6 +278,16 @@ class PyDB:
queue = self.getInternalQueue(thread_id)
queue.put(int_cmd)
+ def checkOutputRedirect(self):
+ global bufferStdOutToServer
+ global bufferStdErrToServer
+
+ if bufferStdOutToServer:
+ self.checkOutput(sys.stdoutBuf, 1) #@UndefinedVariable
+
+ if bufferStdErrToServer:
+ self.checkOutput(sys.stderrBuf, 2) #@UndefinedVariable
+
def checkOutput(self, out, outCtx):
'''Checks the output to see if we have to send some buffered output to the debug server
@@ -287,6 +297,7 @@ class PyDB:
try:
v = out.getvalue()
+
if v:
self.cmdFactory.makeIoMessage(v, outCtx, self)
except:
@@ -299,11 +310,8 @@ class PyDB:
self.acquire()
try:
- if bufferStdOutToServer:
- self.checkOutput(sys.stdoutBuf, 1) #@UndefinedVariable
-
- if bufferStdErrToServer:
- self.checkOutput(sys.stderrBuf, 2) #@UndefinedVariable
+
+ self.checkOutputRedirect()
currThreadId = GetThreadId(threadingCurrentThread())
threads = threadingEnumerate()
@@ -609,10 +617,12 @@ class PyDB:
elif cmd_id == CMD_ADD_EXCEPTION_BREAK:
global exception_set
exception, notify_always, notify_on_terminate = text.split('\t', 2)
- exc_type = get_class(exception)
+
is_notify_always = int(notify_always) == 1
is_notify_on_terminate = int(notify_on_terminate) == 1
+ exc_type = get_class(exception)
+
if exc_type is not None:
exception_set.add(ExceptionBreakpoint(exc_type, is_notify_always, is_notify_on_terminate))
@@ -627,10 +637,22 @@ class PyDB:
exception = text
exc_type = get_class(exception)
if exc_type is not None:
- exception_set.remove(exc_type)
- always_exception_set.remove(exc_type)
+ try:
+ exception_set.remove(exc_type)
+ always_exception_set.remove(exc_type)
+ except:
+ pass
update_exception_hook()
+ elif cmd_id == CMD_LOAD_SOURCE:
+ path = text
+ try:
+ f = open(path, 'r')
+ source = f.read()
+ self.cmdFactory.makeLoadSourceMessage(seq, source, self)
+ except:
+ return self.cmdFactory.makeErrorMessage(seq, GetExceptionTracebackStr())
+
else:
#I have no idea what this is all about
cmd = self.cmdFactory.makeErrorMessage(seq, "unexpected command " + str(cmd_id))
@@ -988,6 +1010,9 @@ def SetTraceForParents(frame, dispatch_func):
frame = frame.f_back
del frame
+def exit_hook():
+ GetGlobalDebugger().checkOutputRedirect()
+
def settrace(host='localhost', stdoutToServer=False, stderrToServer=False, port=5678, suspend=True, trace_only_current_thread=True):
'''Sets the tracing function with the pydev debug function and initializes needed facilities.
@@ -1063,6 +1088,8 @@ def settrace(host='localhost', stdoutToServer=False, stderrToServer=False, port=
thread.start_new = pydev_start_new_thread
except:
pass
+
+ #sys.exitfunc = exit_hook
PyDBCommandThread(debugger).start()
diff --git a/python/helpers/pydev/pydevd_breakpoints.py b/python/helpers/pydev/pydevd_breakpoints.py
index 8d53edfa7155..bc5a2ddaa40f 100644
--- a/python/helpers/pydev/pydevd_breakpoints.py
+++ b/python/helpers/pydev/pydevd_breakpoints.py
@@ -115,7 +115,10 @@ def get_class( kls ):
module = "builtins"
else:
module = "__builtin__"
- m = __import__( module )
- for comp in parts[-1:]:
- m = getattr(m, comp)
- return m
\ No newline at end of file
+ try:
+ m = __import__( module )
+ for comp in parts[-1:]:
+ m = getattr(m, comp)
+ return m
+ except ImportError:
+ return None
\ No newline at end of file
diff --git a/python/helpers/pydev/pydevd_comm.py b/python/helpers/pydev/pydevd_comm.py
index 2ed09dd022c4..ee0bd8ab796c 100644
--- a/python/helpers/pydev/pydevd_comm.py
+++ b/python/helpers/pydev/pydevd_comm.py
@@ -77,6 +77,7 @@ import pydevd_tracing
import pydevd_vm_type
import pydevd_file_utils
import traceback
+from pydevd_utils import *
from pydevd_tracing import GetExceptionTracebackStr
@@ -103,7 +104,8 @@ CMD_RELOAD_CODE = 119
CMD_GET_COMPLETIONS = 120
CMD_CONSOLE_EXEC = 121
CMD_ADD_EXCEPTION_BREAK = 122
-CMD_REMOVE_EXCEPTION_BREAK = 122
+CMD_REMOVE_EXCEPTION_BREAK = 123
+CMD_LOAD_SOURCE = 124
CMD_VERSION = 501
CMD_RETURN = 502
CMD_ERROR = 901
@@ -140,7 +142,7 @@ ID_TO_MEANING = {
MAX_IO_MSG_SIZE = 1000 #if the io is too big, we'll not send all (could make the debugger too non-responsive)
#this number can be changed if there's need to do so
-VERSION_STRING = "1.1"
+VERSION_STRING = "@@BUILD_NUMBER@@"
#--------------------------------------------------------------------------------------------------- UTILITIES
@@ -210,6 +212,7 @@ class PyDBDaemonThread(threading.Thread):
def doKill(self):
#that was not working very well because jython gave some socket errors
self.killReceived = True
+
#=======================================================================================================================
# ReaderThread
@@ -231,8 +234,7 @@ class ReaderThread(PyDBDaemonThread):
except:
#just ignore that
pass
-
-
+
def OnRun(self):
pydevd_tracing.SetTrace(None) # no debugging on this thread
buffer = ""
@@ -258,7 +260,13 @@ class ReaderThread(PyDBDaemonThread):
command, buffer = buffer.split('\n', 1)
PydevdLog(1, "received command ", command)
args = command.split('\t', 2)
- GlobalDebuggerHolder.globalDbg.processNetCommand(int(args[0]), int(args[1]), args[2])
+ try:
+ GlobalDebuggerHolder.globalDbg.processNetCommand(int(args[0]), int(args[1]), args[2])
+ except:
+ traceback.print_exc()
+ sys.stderr.write("Can't process net command %" % command)
+ sys.stderr.flush()
+
except:
traceback.print_exc()
GlobalDebuggerHolder.globalDbg.FinishDebuggingSession()
@@ -545,6 +553,14 @@ class NetCommandFactory:
except Exception:
return self.makeErrorMessage(seq, GetExceptionTracebackStr())
+ def makeLoadSourceMessage(self, seq, source, dbg=None):
+ try:
+ net = NetCommand(str(CMD_LOAD_SOURCE), seq, '%s' % source)
+ if dbg:
+ dbg.writer.addCommand(net)
+ except:
+ return self.makeErrorMessage(0, GetExceptionTracebackStr())
+
INTERNAL_TERMINATE_THREAD = 1
INTERNAL_SUSPEND_THREAD = 2
@@ -600,9 +616,13 @@ class InternalGetVariable(InternalThreadCommand):
valDict = pydevd_vars.resolveCompoundVariable(self.thread_id, self.frame_id, self.scope, self.attributes)
keys = valDict.keys()
if hasattr(keys, 'sort'):
- keys.sort() #Python 3.0 does not have it
+ keys.sort(compare_object_attrs) #Python 3.0 does not have it
else:
- keys = sorted(keys, key=str) #Jython 2.1 does not have it (and all must be compared as strings).
+ if (IS_PY3K):
+ keys = sorted(keys, key=cmp_to_key(compare_object_attrs)) #Jython 2.1 does not have it (and all must be compared as strings).
+ else:
+ keys = sorted(keys, cmp=compare_object_attrs) #Jython 2.1 does not have it (and all must be compared as strings).
+
for k in keys:
xml += pydevd_vars.varToXML(valDict[k], str(k))
diff --git a/python/helpers/pydev/pydevd_utils.py b/python/helpers/pydev/pydevd_utils.py
new file mode 100644
index 000000000000..941414c41984
--- /dev/null
+++ b/python/helpers/pydev/pydevd_utils.py
@@ -0,0 +1,72 @@
+from numbers import *
+import pydevd_constants
+
+def to_number(x):
+ if isinstance(x, Number):
+ return x
+
+ if (is_string(x)):
+ try:
+ n = float(x)
+ return n
+ except ValueError:
+ pass
+
+ l = x.find('(')
+ if (l != -1):
+ y = x[0:l-1]
+ #print y
+ try:
+ n = float(y)
+ return n
+ except ValueError:
+ pass
+ return None
+
+def compare_object_attrs(x, y):
+ try:
+ if (x == y):
+ return 0
+ x_num = to_number(x)
+ y_num = to_number(y)
+ if (x_num is not None and y_num is not None):
+ if x_num - y_num<0:
+ return -1
+ else:
+ return 1
+ if ('__len__' == x):
+ return -1
+ if ('__len__' == y):
+ return 1
+
+ return x.__cmp__(y)
+ except:
+ if pydevd_constants.IS_PY3K:
+ return (str(x) > str(y)) - (str(x) < str(y))
+ else:
+ return cmp(str(x), str(y))
+
+def cmp_to_key(mycmp):
+ 'Convert a cmp= function into a key= function'
+ class K(object):
+ def __init__(self, obj, *args):
+ self.obj = obj
+ def __lt__(self, other):
+ return mycmp(self.obj, other.obj) < 0
+ def __gt__(self, other):
+ return mycmp(self.obj, other.obj) > 0
+ def __eq__(self, other):
+ return mycmp(self.obj, other.obj) == 0
+ def __le__(self, other):
+ return mycmp(self.obj, other.obj) <= 0
+ def __ge__(self, other):
+ return mycmp(self.obj, other.obj) >= 0
+ def __ne__(self, other):
+ return mycmp(self.obj, other.obj) != 0
+ return K
+
+def is_string(x):
+ if (pydevd_constants.IS_PY3K):
+ return isinstance(x, str)
+ else:
+ return isinstance(x, basestring)
\ No newline at end of file
diff --git a/python/helpers/pydev/unittest2.py b/python/helpers/pydev/unittest2.py
deleted file mode 100644
index 98b28268ec2f..000000000000
--- a/python/helpers/pydev/unittest2.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env python
-from unittest import TestResult
-
-class TestListener:
- """ Simulate a Java interface by providing an abstract class
- All methods need to be implemented by classes extending TestListener
- """
-
- # Test was successful.
- def addSuccess(self, test):
- raise NotImplementedError, "TestListener.addSuccess()"
-
- # An error occured
- def addError(self, test, err):
- raise NotImplementedError, "TestListener.addError()"
-
- # A failure occurred.
- def addFailure(self, test, err):
- raise NotImplementedError, "TestListener.addFailure()"
-
- # A test started.
- def startTest(self, test):
- raise NotImplementedError, "TestListener.startTest()"
-
- # A test ended.
- def endTest(self, test):
- raise NotImplementedError, "TestListener.endTest()"
-
-class TestResultWithListeners(TestResult):
- def __init__(self):
- TestResult.__init__(self)
- self.listeners = []
-
- def startTest(self, test):
- TestResult.startTest(self, test)
- for listener in self.listeners:
- listener.startTest(test)
-
- def endTest(self, test):
- for listener in self.listeners:
- listener.endTest(test)
-
- def addSuccess(self, test):
- TestResult.addSuccess(self, test)
- for listener in self.listeners:
- listener.addSuccess(test)
-
- def addError(self, test, err):
- TestResult.addError(self, test, err)
- for listener in self.listeners:
- listener.addError(test, err)
-
- def addFailure(self, test, err):
- TestResult.addFailure(self, test, err)
- for listener in self.listeners:
- listener.addFailure(test, err)
-
- def addListener(self, listener):
- self.listeners.append(listener)
-
- def removeListener(self, listener):
- self.listeners.remove(listener)
-
diff --git a/python/layout.xml b/python/layout.xml
index 1b854d960c22..7a292f8eebd7 100644
--- a/python/layout.xml
+++ b/python/layout.xml
@@ -17,6 +17,7 @@
+
diff --git a/python/pluginSrc/META-INF/plugin.xml b/python/pluginSrc/META-INF/plugin.xml
index 38ca1f131310..3ab02f6d5cf2 100644
--- a/python/pluginSrc/META-INF/plugin.xml
+++ b/python/pluginSrc/META-INF/plugin.xml
@@ -4,9 +4,9 @@
Pythonid
Python
-
+
Smart editing for Python scripts
- 2.6.4
+ 2.6.5
com.intellij.modules.ultimate
com.intellij.modules.java
org.jetbrains.plugins.yaml
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java
index 1fa24d8bbf92..4cd70ae70e64 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java
@@ -133,7 +133,7 @@ public class PyDebugValue extends XValue {
}
catch (PyDebuggerException e) {
if (!node.isObsolete()) {
- node.setErrorMessage("Unable to display children");
+ node.setErrorMessage("Unable to display children:" + e.getMessage());
}
LOG.warn(e);
}
@@ -157,4 +157,8 @@ public class PyDebugValue extends XValue {
return DebuggerIcons.VALUE_ICON;
}
}
+
+ boolean isException() {
+ return myValue.startsWith("Traceback (most recent call last):");
+ }
}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
index 3a4cfad16b35..569eb5095923 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
@@ -2,7 +2,6 @@ package com.jetbrains.python.debugger.pydev;
import com.jetbrains.python.debugger.PyDebuggerException;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
public abstract class AbstractCommand {
@@ -27,6 +26,7 @@ public abstract class AbstractCommand {
public static final int CONSOLE_EXEC = 121;
public static final int ADD_EXCEPTION_BREAKPOINT = 122;
public static final int REMOVE_EXCEPTION_BREAKPOINT = 123;
+ public static final int LOAD_SOURCE = 124;
public static final int VERSION = 501;
public static final String NEW_LINE_CHAR = "@_@NEW_LINE_CHAR@_@";
public static final String TAB_CHAR = "@_@TAB_CHAR@_@";
@@ -39,7 +39,7 @@ public abstract class AbstractCommand {
myCommandCode = commandCode;
}
- @Nullable
+ @NotNull
public final String getPayload() {
Payload payload = new Payload();
buildPayload(payload);
@@ -52,7 +52,6 @@ public abstract class AbstractCommand {
return false;
}
- // todo: pass debugger to constructor(?)
public void execute() throws PyDebuggerException {
int sequence = myDebugger.getNextSequence();
if (isResponseExpected()) {
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ChangeVariableCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ChangeVariableCommand.java
index ed04531e0f04..edf6b863c232 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ChangeVariableCommand.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ChangeVariableCommand.java
@@ -23,7 +23,8 @@ public class ChangeVariableCommand extends AbstractFrameCommand {
@Override
protected void buildPayload(Payload payload) {
- payload.add("FRAME").add(myVariableName).add(myValue);
+ super.buildPayload(payload);
+ payload.add("FRAME").add(myVariableName).add(ProtocolParser.encodeExpression(myValue));
}
@Override
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/EvaluateCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/EvaluateCommand.java
index 1d08a865f4c5..113a6b3bbb7a 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/EvaluateCommand.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/EvaluateCommand.java
@@ -27,7 +27,6 @@ public class EvaluateCommand extends AbstractFrameCommand {
protected void buildPayload(Payload payload) {
super.buildPayload(payload);
payload.add("FRAME").add(myExpression).add(myTrimResult);
-
}
@Override
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/LoadSourceCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/LoadSourceCommand.java
new file mode 100644
index 000000000000..cdafeae93a52
--- /dev/null
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/LoadSourceCommand.java
@@ -0,0 +1,39 @@
+package com.jetbrains.python.debugger.pydev;
+
+import com.jetbrains.python.debugger.PyDebuggerException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author traff
+ */
+public class LoadSourceCommand extends AbstractCommand {
+ private final String myPath;
+
+ private String myContent = null;
+
+ protected LoadSourceCommand(@NotNull final RemoteDebugger debugger, String path) {
+ super(debugger, LOAD_SOURCE);
+ myPath = path;
+ }
+
+ public boolean isResponseExpected() {
+ return true;
+ }
+
+ @Override
+ protected void processResponse(final ProtocolFrame response) throws PyDebuggerException {
+ super.processResponse(response);
+ myContent = ProtocolParser.parseSourceContent(response.getPayload());
+ }
+
+ @Override
+ protected void buildPayload(Payload payload) {
+ payload.add(myPath);
+ }
+
+ @Nullable
+ public String getContent() {
+ return myContent;
+ }
+}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java
index 69cd30e6ab41..929a5893f281 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java
@@ -17,6 +17,10 @@ public class ProtocolParser {
private ProtocolParser() {
}
+ public static String parseSourceContent(String payload) throws PyDebuggerException {
+ return payload;
+ }
+
public static String decode(final String value) throws PyDebuggerException {
try {
return URLDecoder.decode(value, "UTF-8");
@@ -121,10 +125,6 @@ public class ProtocolParser {
value = value.substring(type.length() + 2);
}
- if (value.startsWith("Traceback (most recent call last):")) { // convert traceback to exception
- throw new PyDebuggerException(value);
- }
-
return new PyDebugValue(name, type, value, "True".equals(isContainer));
}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
index 1c6032b0d4b9..df0049616dbd 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
@@ -5,6 +5,7 @@
*/
package com.jetbrains.python.debugger.pydev;
+import com.google.common.collect.Lists;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -42,6 +43,9 @@ public class RemoteDebugger {
private final Map myResponseQueue = new HashMap();
private final TempVarsHolder myTempVars = new TempVarsHolder();
+
+ private final List myCloseListeners = Lists.newArrayList();
+
public RemoteDebugger(final IPyDebugProcess debugProcess, final ServerSocket serverSocket, final int timeout) {
myDebugProcess = debugProcess;
myServerSocket = serverSocket;
@@ -56,6 +60,7 @@ public class RemoteDebugger {
return myConnected;
}
+
public void waitForConnect() throws Exception {
try {
//noinspection SocketOpenedButNotSafelyClosed
@@ -147,6 +152,18 @@ public class RemoteDebugger {
return command.getNewValue();
}
+ @Nullable
+ public String loadSource(String path) {
+ LoadSourceCommand command = new LoadSourceCommand(this, path);
+ try {
+ command.execute();
+ return command.getContent();
+ }
+ catch (PyDebuggerException e) {
+ return "#Couldn't load source of file " + path;
+ }
+ }
+
private static String composeName(final PyDebugValue var) {
final StringBuilder sb = new StringBuilder(var.getTempName());
PyDebugValue p = var;
@@ -267,8 +284,8 @@ public class RemoteDebugger {
os.flush();
}
catch (SocketException se) {
- LOG.error(se);
disconnect();
+ fireCloseEvent();
}
catch (IOException e) {
LOG.error(e);
@@ -303,6 +320,7 @@ public class RemoteDebugger {
}
}
disconnect();
+ fireCloseEvent();
}
@@ -322,7 +340,7 @@ public class RemoteDebugger {
}
}
catch (SocketException ignore) {
- // disconnected
+ fireCloseEvent();
}
catch (Exception e) {
LOG.error(e);
@@ -458,4 +476,18 @@ public class RemoteDebugger {
}
}
}
+
+ public void addCloseListener(RemoteDebuggerCloseListener listener) {
+ myCloseListeners.add(listener);
+ }
+
+ public void remoteCloseListener(RemoteDebuggerCloseListener listener) {
+ myCloseListeners.remove(listener);
+ }
+
+ private void fireCloseEvent() {
+ for (RemoteDebuggerCloseListener listener : myCloseListeners) {
+ listener.closed();
+ }
+ }
}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebuggerCloseListener.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebuggerCloseListener.java
new file mode 100644
index 000000000000..1a4968a5c3c7
--- /dev/null
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebuggerCloseListener.java
@@ -0,0 +1,8 @@
+package com.jetbrains.python.debugger.pydev;
+
+/**
+ * @author traff
+ */
+public interface RemoteDebuggerCloseListener {
+ void closed();
+}
diff --git a/python/python-pydev.iml b/python/python-pydev.iml
index 1b5bd2c15dfc..d5e96f5ff146 100644
--- a/python/python-pydev.iml
+++ b/python/python-pydev.iml
@@ -9,6 +9,7 @@
+
diff --git a/python/resources/pycharm_about.png b/python/resources/pycharm_about.png
index f9b202f3dae0..ef6767fcbd0c 100644
Binary files a/python/resources/pycharm_about.png and b/python/resources/pycharm_about.png differ
diff --git a/python/resources/pycharm_logo.png b/python/resources/pycharm_logo.png
index 32c72e198628..fcbfb765a947 100644
Binary files a/python/resources/pycharm_logo.png and b/python/resources/pycharm_logo.png differ
diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml
index abaa90340882..9a05e17ea07a 100644
--- a/python/src/META-INF/python-plugin-common.xml
+++ b/python/src/META-INF/python-plugin-common.xml
@@ -62,7 +62,7 @@
-
+
@@ -77,6 +77,9 @@
+
+
+
@@ -85,7 +88,8 @@
-
+
+
com.jetbrains.python.codeInsight.intentions.ImportToImportFromIntention
@@ -355,6 +359,9 @@
+
+
diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index 1e21c45c4bde..20aaa2d7ba04 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -458,6 +458,14 @@ QDOC.module.path.unknown=(Module path is unknown)
runcfg.doctest.display_name=Python's doctests
runcfg.doctest.description=Python's doctests run configuration
+### nosetests run conf
+runcfg.nosetests.display_name=Python's nosetests
+runcfg.nosetests.description=Python's nosetests run configuration
+
+### pytest run conf
+runcfg.pytest.display_name=py.test
+runcfg.pytest.description=py.test run configuration
+
### unittest run configuration
runcfg.unittest.display_name=Python's unittest
@@ -515,12 +523,18 @@ run.configuration.remote.debug.name=Python Remote Debug
run.configuration.type.description=Starts server for remote debug
remote.debug.info=Info
-remote.debug.server.hint=Run this remote debug configuration to start debug server.
-remote.debug.server.hint2=Then copy \\helpers\\pydev package from your PyCharm distribution to location of your python script.
-remote.debug.server.hint3=Use the following command in your script to connect to debug server:
+remote.debug.server.hint=Launch this debug configuration to start the debug server.
+remote.debug.server.hint1.5=Update your script:
+remote.debug.server.hint2=1. Add pycharm-debug.egg from the PyCharm installation to the Python path.
+remote.debug.server.hint2.5=2. Add the following import statement:
+remote.debug.server.hint3=3. Add the following command to connect to the debug server:
remote.debug.settings=Settings
remote.debug.remote.host=Remote host
-remote.debug.port=Port
-remote.debug.remote.root.folder=Remote root folder
-remote.debug.local.root.folder=Local root folder
+remote.debug.port=Port:
+remote.debug.use.path.mapping=Use path mapping
+remote.debug.remote.root.folder=Remote paths prefix:
+remote.debug.local.root.folder=Local paths prefix:
remote.debug.server.hint4=If you want to enable stdout and stderr redirection to PyCharm console, use following command in your script:
+remote.debug.local.host=Local host name:
+remote.debug.redirect.output=Redirect output to console
+remote.debug.suspend.on.connect=Suspend after connect
diff --git a/python/src/com/jetbrains/python/PyElementTypes.java b/python/src/com/jetbrains/python/PyElementTypes.java
index 089a5219b0a0..354be964d521 100644
--- a/python/src/com/jetbrains/python/PyElementTypes.java
+++ b/python/src/com/jetbrains/python/PyElementTypes.java
@@ -128,7 +128,7 @@ public interface PyElementTypes {
PyTokenTypes.OR, PyTokenTypes.XOR, PyTokenTypes.AND, PyTokenTypes.LTLT, PyTokenTypes.GTGT,
PyTokenTypes.EQEQ, PyTokenTypes.GE, PyTokenTypes.LE, PyTokenTypes.NE, PyTokenTypes.NE_OLD,
PyTokenTypes.IN_KEYWORD, PyTokenTypes.IS_KEYWORD, PyTokenTypes.NOT_KEYWORD, PyTokenTypes.PLUS,
- PyTokenTypes.MINUS, PyTokenTypes.MULT, PyTokenTypes.FLOORDIV, PyTokenTypes.DIV, PyTokenTypes.PERC);
+ PyTokenTypes.MINUS, PyTokenTypes.MULT, PyTokenTypes.FLOORDIV, PyTokenTypes.DIV, PyTokenTypes.PERC, PyTokenTypes.EXP);
TokenSet UNARY_OPS = TokenSet.create(PyTokenTypes.NOT_KEYWORD, PyTokenTypes.PLUS, PyTokenTypes.MINUS, PyTokenTypes.TILDE);
diff --git a/python/src/com/jetbrains/python/PythonBundledColorSchemeProvider.java b/python/src/com/jetbrains/python/PythonBundledColorSchemeProvider.java
index 7b78adb700f3..728258a70c93 100644
--- a/python/src/com/jetbrains/python/PythonBundledColorSchemeProvider.java
+++ b/python/src/com/jetbrains/python/PythonBundledColorSchemeProvider.java
@@ -13,6 +13,8 @@ public class PythonBundledColorSchemeProvider implements BundledColorSchemesProv
public static final String[] PATHS = {
"/colorSchemes/WarmNeon",
+ "/colorSchemes/monokai",
+ "/colorSchemes/twilight",
};
@Override
diff --git a/python/src/com/jetbrains/python/PythonFoldingBuilder.java b/python/src/com/jetbrains/python/PythonFoldingBuilder.java
index c26768511b43..ada510dc311a 100644
--- a/python/src/com/jetbrains/python/PythonFoldingBuilder.java
+++ b/python/src/com/jetbrains/python/PythonFoldingBuilder.java
@@ -53,11 +53,17 @@ public class PythonFoldingBuilder implements FoldingBuilder, DumbAware {
if (elType == PyElementTypes.FUNCTION_DECLARATION || elType == PyElementTypes.CLASS_DECLARATION) {
ASTNode colon = node.getTreeParent().findChildByType(PyTokenTypes.COLON);
if (colon != null && colon.getStartOffset() + 1 < node.getTextRange().getEndOffset() - 1) {
- descriptors.add(new FoldingDescriptor(node, new TextRange(colon.getStartOffset() + 1, node.getTextRange().getEndOffset())));
+ final CharSequence chars = node.getChars();
+ int nodeStart = node.getTextRange().getStartOffset();
+ int endOffset = node.getTextRange().getEndOffset();
+ while(endOffset > colon.getStartOffset()+2 && endOffset > nodeStart && Character.isWhitespace(chars.charAt(endOffset - nodeStart - 1))) {
+ endOffset--;
+ }
+ descriptors.add(new FoldingDescriptor(node, new TextRange(colon.getStartOffset() + 1, endOffset)));
}
else {
TextRange range = node.getTextRange();
- if (range.getStartOffset() < range.getEndOffset() - 1) { // only for ranges at leas 1 char wide
+ if (range.getStartOffset() < range.getEndOffset() - 1) { // only for ranges at least 1 char wide
descriptors.add(new FoldingDescriptor(node, range));
}
}
diff --git a/python/src/com/jetbrains/python/actions/AddFunctionQuickFix.java b/python/src/com/jetbrains/python/actions/AddFunctionQuickFix.java
index f0f0750696a4..9a9f9ca56556 100644
--- a/python/src/com/jetbrains/python/actions/AddFunctionQuickFix.java
+++ b/python/src/com/jetbrains/python/actions/AddFunctionQuickFix.java
@@ -13,9 +13,6 @@ import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.ParamHelper;
import com.jetbrains.python.psi.impl.PyFunctionBuilder;
-import com.jetbrains.python.psi.types.PyClassType;
-import com.jetbrains.python.psi.types.PyType;
-import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import static com.jetbrains.python.psi.PyUtil.sure;
@@ -75,7 +72,7 @@ public class AddFunctionQuickFix implements LocalQuickFix {
builder.parameter("param");
}
}
- PyFunction function = builder.buildFunction(project);
+ PyFunction function = builder.buildFunction(project, LanguageLevel.getDefault());
// add to the bottom
function = (PyFunction) myPyFile.add(function);
diff --git a/python/src/com/jetbrains/python/actions/AddIgnoredIdentifierFix.java b/python/src/com/jetbrains/python/actions/AddIgnoredIdentifierFix.java
index 1eb3b7bfb2bb..53cec5ae626e 100644
--- a/python/src/com/jetbrains/python/actions/AddIgnoredIdentifierFix.java
+++ b/python/src/com/jetbrains/python/actions/AddIgnoredIdentifierFix.java
@@ -1,5 +1,6 @@
package com.jetbrains.python.actions;
+import com.intellij.codeInsight.intention.LowPriorityAction;
import com.intellij.codeInspection.InspectionProfile;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
@@ -12,7 +13,7 @@ import org.jetbrains.annotations.NotNull;
/**
* @author yole
*/
-public class AddIgnoredIdentifierFix implements LocalQuickFix {
+public class AddIgnoredIdentifierFix implements LocalQuickFix, LowPriorityAction {
private final String myIdentifier;
public AddIgnoredIdentifierFix(String identifier) {
diff --git a/python/src/com/jetbrains/python/actions/AddImportHelper.java b/python/src/com/jetbrains/python/actions/AddImportHelper.java
index 96a687e6b85c..6ac9157aab54 100644
--- a/python/src/com/jetbrains/python/actions/AddImportHelper.java
+++ b/python/src/com/jetbrains/python/actions/AddImportHelper.java
@@ -91,7 +91,14 @@ public class AddImportHelper {
if (relativeToName == null) {
return false;
}
- ImportPriority relativeToPriority = source == null ? ImportPriority.BUILTIN : getImportPriority(file, source.getContainingFile());
+ final PsiFileSystemItem containingFile;
+ if (source instanceof PsiDirectory) {
+ containingFile = (PsiDirectory) source;
+ }
+ else {
+ containingFile = source != null ? source.getContainingFile() : null;
+ }
+ ImportPriority relativeToPriority = source == null ? ImportPriority.BUILTIN : getImportPriority(file, containingFile);
final int rc = priority.compareTo(relativeToPriority);
if (rc < 0) {
return true;
diff --git a/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java b/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java
index caee655f964c..4173532f766c 100644
--- a/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java
+++ b/python/src/com/jetbrains/python/actions/AddMethodQuickFix.java
@@ -100,7 +100,7 @@ public class AddMethodQuickFix implements LocalQuickFix {
builder.parameter("param");
}
}
- PyFunction meth = builder.buildFunction(project);
+ PyFunction meth = builder.buildFunction(project, LanguageLevel.getDefault());
if (deco_name != null) {
PyElementGenerator generator = PyElementGenerator.getInstance(project);
PyDecoratorList deco_list = generator.createFromText(LanguageLevel.getDefault(), PyDecoratorList.class, "@" + deco_name + "\ndef foo(): pass", new int[]{0, 0});
diff --git a/python/src/com/jetbrains/python/actions/AugmentedAssignmentQuickFix.java b/python/src/com/jetbrains/python/actions/AugmentedAssignmentQuickFix.java
index aa45be55829a..37a8d38ec04c 100644
--- a/python/src/com/jetbrains/python/actions/AugmentedAssignmentQuickFix.java
+++ b/python/src/com/jetbrains/python/actions/AugmentedAssignmentQuickFix.java
@@ -9,9 +9,6 @@ import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyAugAssignmentStatementImpl;
import org.jetbrains.annotations.NotNull;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* User: catherine
*
@@ -36,25 +33,30 @@ public class AugmentedAssignmentQuickFix implements LocalQuickFix {
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getPsiElement();
- if (element != null && element instanceof PyAssignmentStatement && element.isWritable()) {
+ if (element instanceof PyAssignmentStatement && element.isWritable()) {
PyAssignmentStatement statement = (PyAssignmentStatement)element;
- PyTargetExpression target = ((PyTargetExpression)statement.getLeftHandSideExpression());
+ PyExpression target = statement.getLeftHandSideExpression();
PyBinaryExpression expression = (PyBinaryExpression)statement.getAssignedValue();
PyExpression leftExpression = expression.getLeftExpression();
PyExpression rightExpression = expression.getRightExpression();
-
- if (leftExpression != null && leftExpression instanceof PyReferenceExpression) {
- if (leftExpression.getName().equals(target.getName())) {
+ if (rightExpression.getText().equals(target.getText())) {
+ PyExpression tmp = rightExpression;
+ rightExpression = leftExpression;
+ leftExpression = tmp;
+ }
+ if (leftExpression != null
+ && (leftExpression instanceof PyReferenceExpression || leftExpression instanceof PySubscriptionExpression)) {
+ if (leftExpression.getText().equals(target.getText())) {
if (rightExpression instanceof PyNumericLiteralExpression ||
rightExpression instanceof PyStringLiteralExpression
|| rightExpression instanceof PyReferenceExpression) {
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append(target.getName()).append(" ").
+ stringBuilder.append(target.getText()).append(" ").
append(expression.getPsiOperator().getText()).append("= ").append(rightExpression.getText());
- PyAugAssignmentStatementImpl augAssignment = elementGenerator.createFromText(LanguageLevel.getDefault(),
+ PyAugAssignmentStatementImpl augAssignment = elementGenerator.createFromText(LanguageLevel.forElement(element),
PyAugAssignmentStatementImpl.class, stringBuilder.toString());
statement.replace(augAssignment);
}
diff --git a/python/src/com/jetbrains/python/actions/ChainedComparisonsQuickFix.java b/python/src/com/jetbrains/python/actions/ChainedComparisonsQuickFix.java
index b23f7ee08290..a9b806ce26d1 100644
--- a/python/src/com/jetbrains/python/actions/ChainedComparisonsQuickFix.java
+++ b/python/src/com/jetbrains/python/actions/ChainedComparisonsQuickFix.java
@@ -50,14 +50,14 @@ public class ChainedComparisonsQuickFix implements LocalQuickFix {
if (leftExpression.getRightExpression() instanceof PyBinaryExpression) {
checkOperator((PyBinaryExpression)leftExpression.getRightExpression(), rightExpression, project);
}
- else if (leftExpression.getOperator() == rightExpression.getOperator() &&
+ else if (/*leftExpression.getOperator() == rightExpression.getOperator() && */
PyTokenTypes.RELATIONAL_OPERATIONS.contains(leftExpression.getOperator())) {
PyExpression leftRight = leftExpression.getRightExpression();
if (leftRight != null) {
if (leftRight.getText().equals(getSmallLeftExpression(rightExpression).getText())) {
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
PyBinaryExpression binaryExpression = elementGenerator.createBinaryExpression(
- (leftExpression).getPsiOperator().getText(), leftExpression,
+ (rightExpression).getPsiOperator().getText(), leftExpression,
getLargeRightExpression(rightExpression, project));
leftExpression.replace(binaryExpression);
rightExpression.delete();
diff --git a/python/src/com/jetbrains/python/actions/UnresolvedRefCreateFunctionQuickFix.java b/python/src/com/jetbrains/python/actions/UnresolvedRefCreateFunctionQuickFix.java
index 55c2176b4aad..53f3f34d5588 100644
--- a/python/src/com/jetbrains/python/actions/UnresolvedRefCreateFunctionQuickFix.java
+++ b/python/src/com/jetbrains/python/actions/UnresolvedRefCreateFunctionQuickFix.java
@@ -7,8 +7,6 @@ import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.*;
@@ -56,13 +54,20 @@ public class UnresolvedRefCreateFunctionQuickFix implements LocalQuickFix {
functionBuilder.parameter("param");
}
}
- PyFunction function = functionBuilder.buildFunction(project);
+ PyFunction function = functionBuilder.buildFunction(project, LanguageLevel.getDefault());
PyFunction parentFunction = PsiTreeUtil.getTopmostParentOfType(myElement, PyFunction.class);
if (parentFunction != null ) {
- PsiFile file = myElement.getContainingFile();
- function = (PyFunction)file.addBefore(function, parentFunction);
+ PyClass parentClass = PsiTreeUtil.getTopmostParentOfType(parentFunction, PyClass.class);
+ if (parentClass != null) {
+ PsiElement parent = parentClass.getParent();
+ function = (PyFunction)parent.addBefore(function, parentClass);
+ } else {
+ PsiElement parent = parentFunction.getParent();
+ function = (PyFunction)parent.addBefore(function, parentFunction);
+ }
} else {
- PyStatement statement = PsiTreeUtil.getParentOfType(myElement, PyStatement.class);
+ PyStatement statement = PsiTreeUtil.getTopmostParentOfType(myElement,
+ PyStatement.class);
if (statement != null) {
PsiElement parent = statement.getParent();
if (parent != null)
diff --git a/python/src/com/jetbrains/python/buildout/config/BuildoutCfgColorsPage.java b/python/src/com/jetbrains/python/buildout/config/BuildoutCfgColorsPage.java
new file mode 100644
index 000000000000..664820810415
--- /dev/null
+++ b/python/src/com/jetbrains/python/buildout/config/BuildoutCfgColorsPage.java
@@ -0,0 +1,77 @@
+package com.jetbrains.python.buildout.config;
+
+import com.intellij.openapi.editor.colors.TextAttributesKey;
+import com.intellij.openapi.fileTypes.SyntaxHighlighter;
+import com.intellij.openapi.options.colors.AttributesDescriptor;
+import com.intellij.openapi.options.colors.ColorDescriptor;
+import com.intellij.openapi.options.colors.ColorSettingsPage;
+import com.intellij.util.containers.HashMap;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.Map;
+
+/**
+ * @author traff
+ */
+public class BuildoutCfgColorsPage implements ColorSettingsPage {
+ private static final AttributesDescriptor[] ATTRS = new AttributesDescriptor[]{
+ new AttributesDescriptor("Section name", BuildoutCfgSyntaxHighlighter.BUILDOUT_SECTION_NAME),
+ new AttributesDescriptor("Key", BuildoutCfgSyntaxHighlighter.BUILDOUT_KEY),
+ new AttributesDescriptor("Value", BuildoutCfgSyntaxHighlighter.BUILDOUT_VALUE),
+ new AttributesDescriptor("Key value separator", BuildoutCfgSyntaxHighlighter.BUILDOUT_KEY_VALUE_SEPARATOR),
+ new AttributesDescriptor("Comment", BuildoutCfgSyntaxHighlighter.BUILDOUT_COMMENT)
+ };
+
+ @NonNls private static final HashMap ourTagToDescriptorMap = new HashMap();
+
+ static {
+ //ourTagToDescriptorMap.put("comment", DjangoTemplateHighlighterColors.DJANGO_COMMENT);
+ }
+
+ @NotNull
+ public String getDisplayName() {
+ return "Buildout config";
+ }
+
+ public Icon getIcon() {
+ return BuildoutCfgFileType.INSTANCE.getIcon();
+ }
+
+ @NotNull
+ public AttributesDescriptor[] getAttributeDescriptors() {
+ return ATTRS;
+ }
+
+ @NotNull
+ public ColorDescriptor[] getColorDescriptors() {
+ return ColorDescriptor.EMPTY_ARRAY;
+ }
+
+ @NotNull
+ public SyntaxHighlighter getHighlighter() {
+ final SyntaxHighlighter highlighter = SyntaxHighlighter.PROVIDER.create(BuildoutCfgFileType.INSTANCE, null, null);
+ assert highlighter != null;
+ return highlighter;
+ }
+
+ @NotNull
+ public String getDemoText() {
+ return
+ "; Buildout config\n"+
+ "[buildout]\n" +
+ "parts = python\n" +
+ "develop = .\n" +
+ "eggs = django-shorturls\n" +
+ "\n" +
+ "[python]\n" +
+ "recipe = zc.recipe.egg\n" +
+ "interpreter = python\n" +
+ "eggs = ${buildout:eggs}";
+ }
+
+ public Map getAdditionalHighlightingTagToDescriptorMap() {
+ return ourTagToDescriptorMap;
+ }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/PyDictKeyNamesCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/PyDictKeyNamesCompletionContributor.java
index f0374d7e9820..2f14658369a4 100644
--- a/python/src/com/jetbrains/python/codeInsight/PyDictKeyNamesCompletionContributor.java
+++ b/python/src/com/jetbrains/python/codeInsight/PyDictKeyNamesCompletionContributor.java
@@ -4,6 +4,7 @@ import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.lang.ASTNode;
+import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
@@ -45,7 +46,8 @@ public class PyDictKeyNamesCompletionContributor extends PySeeingOriginalComplet
@NotNull final CompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result
) {
PsiElement original = parameters.getOriginalPosition();
- final CompletionResultSet dictCompletion = createResult(original, result);
+ int offset = parameters.getOffset();
+ final CompletionResultSet dictCompletion = createResult(original, result, offset);
PsiElement operand = PsiTreeUtil.getParentOfType(original, PySubscriptionExpression.class).getOperand();
if (operand != null) {
@@ -68,13 +70,6 @@ public class PyDictKeyNamesCompletionContributor extends PySeeingOriginalComplet
}
}
}
- // filter unusable lookup elements
- dictCompletion.runRemainingContributors(parameters, new Consumer() {
- public void consume(final LookupElement lookupElement) {
- if (lookupElement.getLookupString().equals("dict key"))
- dictCompletion.addElement(lookupElement);
- }
- });
}
}
);
@@ -82,23 +77,25 @@ public class PyDictKeyNamesCompletionContributor extends PySeeingOriginalComplet
/**
* create completion result with prefix matcher if needed
+ *
* @param original is original element
* @param result is initial completion result
+ * @param offset
* @return
*/
- private static CompletionResultSet createResult(PsiElement original, CompletionResultSet result) {
+ private static CompletionResultSet createResult(PsiElement original, CompletionResultSet result, int offset) {
PsiElement prevElement = original.getPrevSibling();
if (prevElement != null) {
ASTNode prevNode = prevElement.getNode();
if (prevNode != null) {
if (prevNode.getElementType() != PyTokenTypes.LBRACKET)
- return result.withPrefixMatcher(findPrefix(prevElement));
+ return result.withPrefixMatcher(findPrefix(prevElement, offset));
}
}
PsiElement parentElement = original.getParent();
if (parentElement != null) {
if (parentElement instanceof PyStringLiteralExpression)
- return result.withPrefixMatcher(findPrefix(parentElement));
+ return result.withPrefixMatcher(findPrefix(parentElement, offset));
}
return result;
}
@@ -108,15 +105,16 @@ public class PyDictKeyNamesCompletionContributor extends PySeeingOriginalComplet
* @param element to find prefix of
* @return prefix
*/
- private static String findPrefix(final PsiElement element) {
+ private static String findPrefix(final PsiElement element, int offset) {
if (element instanceof PyStringLiteralExpression) {
- char[] chs = element.getText().toCharArray();
+ String text = TextRange.create(element.getTextRange().getStartOffset(), offset).substring(element.getContainingFile().getText());
+ char[] chs = text.toCharArray();
char start = chs[0];
StringBuilder builder = new StringBuilder();
builder.append(start);
for (int i = 1; i != chs.length; ++i) {
char ch = chs[i];
- if (ch == ']') break;
+ if (ch == ']' || ch == '\n') break;
if (ch == start) {
break;
}
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertLambdaToFunctionIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertLambdaToFunctionIntention.java
index 3f025d113936..036a9c526393 100644
--- a/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertLambdaToFunctionIntention.java
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyConvertLambdaToFunctionIntention.java
@@ -2,9 +2,8 @@ package com.jetbrains.python.codeInsight.intentions;
import com.intellij.codeInsight.CodeInsightUtilBase;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
-import com.intellij.codeInsight.template.TemplateBuilder;
-import com.intellij.codeInsight.template.TemplateBuilderFactory;
-import com.intellij.codeInsight.template.TemplateBuilderImpl;
+import com.intellij.codeInsight.lookup.LookupElement;
+import com.intellij.codeInsight.template.*;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
@@ -57,29 +56,72 @@ public class PyConvertLambdaToFunctionIntention extends BaseIntentionAction {
functionBuilder.parameter(param.getText());
}
functionBuilder.statement("return " + body.getText());
- PyFunction function = functionBuilder.buildFunction(project);
- PyStatement statement = PsiTreeUtil.getParentOfType(lambdaExpression, PyStatement.class);
- if (statement != null) {
- PsiElement parentOfStatement = statement.getParent();
- if (parentOfStatement != null)
- function = (PyFunction)parentOfStatement.addBefore(function, statement);
+ PyFunction function = functionBuilder.buildFunction(project, LanguageLevel.getDefault());
+
+ PyFunction parentFunction = PsiTreeUtil.getTopmostParentOfType(lambdaExpression, PyFunction.class);
+ if (parentFunction != null ) {
+ PyClass parentClass = PsiTreeUtil.getTopmostParentOfType(parentFunction, PyClass.class);
+ if (parentClass != null) {
+ PsiElement classParent = parentClass.getParent();
+ function = (PyFunction)classParent.addBefore(function, parentClass);
+ } else {
+ PsiElement funcParent = parentFunction.getParent();
+ function = (PyFunction)funcParent.addBefore(function, parentFunction);
+ }
+ } else {
+ PyStatement statement = PsiTreeUtil.getTopmostParentOfType(lambdaExpression,
+ PyStatement.class);
+ if (statement != null) {
+ PsiElement statementParent = statement.getParent();
+ if (statementParent != null)
+ function = (PyFunction)statementParent.addBefore(function, statement);
+ }
}
+ function = CodeInsightUtilBase
+ .forcePsiPostprocessAndRestoreElement(function);
+
if (parent instanceof PyAssignmentStatement) {
parent.delete();
}
else {
- PyElement parentScope = PsiTreeUtil.getParentOfType(lambdaExpression, PyFunction.class, PyClass.class, PyFile.class);
+ PyElement parentScope = PsiTreeUtil.getParentOfType(lambdaExpression, PyClass.class, PyFile.class);
final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(parentScope);
PsiElement functionName = function.getNameIdentifier();
functionName = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(functionName);
lambdaExpression = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(lambdaExpression);
+ ReferenceNameExpression refExpr = new ReferenceNameExpression(name);
+
+ ((TemplateBuilderImpl)builder).replaceElement(lambdaExpression, name, refExpr, true);
((TemplateBuilderImpl)builder).replaceElement(functionName, name, name, false);
- ((TemplateBuilderImpl)builder).replaceElement(lambdaExpression, name, name, true);
+
int textOffSet = functionName.getTextOffset();
+ editor.getCaretModel().moveToOffset(parentScope.getTextRange().getStartOffset());
+
+ Template template = ((TemplateBuilderImpl)builder).buildInlineTemplate();
+ TemplateManager.getInstance(project).startTemplate(editor, template);
editor.getCaretModel().moveToOffset(textOffSet);
- builder.run();
}
}
}
+ private class ReferenceNameExpression extends Expression {
+ ReferenceNameExpression(String oldReferenceName) {
+ myOldReferenceName = oldReferenceName;
+ }
+
+ private final String myOldReferenceName;
+
+ public Result calculateResult(ExpressionContext context) {
+ return new TextResult(myOldReferenceName);
+ }
+
+ public Result calculateQuickResult(ExpressionContext context) {
+ return null;
+ }
+
+ @Override
+ public LookupElement[] calculateLookupItems(ExpressionContext context) {
+ return null;
+ }
+ }
}
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyDictLiteralFormToConstructorIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyDictLiteralFormToConstructorIntention.java
index 2937526ae471..492b5fc995f3 100644
--- a/python/src/com/jetbrains/python/codeInsight/intentions/PyDictLiteralFormToConstructorIntention.java
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyDictLiteralFormToConstructorIntention.java
@@ -37,12 +37,22 @@ public class PyDictLiteralFormToConstructorIntention extends BaseIntentionAction
if (dictExpression != null) {
PyKeyValueExpression[] elements = dictExpression.getElements();
+ boolean canConvert = true;
if (elements.length != 0) {
for (PyKeyValueExpression element : elements) {
- if (! (element.getKey() instanceof PyStringLiteralExpression)) return false;
+ PyExpression key = element.getKey();
+ if (! (key instanceof PyStringLiteralExpression)) return false;
+ String str = ((PyStringLiteralExpression)key).getStringValue();
+ if(Character.isDigit(str.charAt(0))) return false;
+ try {
+ Integer.parseInt(str) ;
+ canConvert = false;
+ } catch (NumberFormatException e) {
+ // pass
+ }
}
}
- return true;
+ if (canConvert) return true;
}
return false;
}
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyJoinIfIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyJoinIfIntention.java
index fbb4716689cf..9b24d8f753b8 100644
--- a/python/src/com/jetbrains/python/codeInsight/intentions/PyJoinIfIntention.java
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyJoinIfIntention.java
@@ -3,6 +3,8 @@ package com.jetbrains.python.codeInsight.intentions;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiComment;
+import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
@@ -11,6 +13,8 @@ import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.List;
+
/**
* User: catherine
* Intention to merge the if clauses in the case of nested ifs where only the inner if contains code (the outer if only contains the inner one)
@@ -64,11 +68,22 @@ public class PyJoinIfIntention extends BaseIntentionAction {
if (firstStatement != null && firstStatement instanceof PyIfStatement) {
PyExpression condition = ((PyIfStatement)firstStatement).getIfPart().getCondition();
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
- PyExpression newCondition = elementGenerator.createExpressionFromText(ifStatement.getIfPart().getCondition().getText() + " and " + condition.getText());
+ PyExpression newCondition = elementGenerator.createExpressionFromText(
+ ifStatement.getIfPart().getCondition().getText() + " and " + condition.getText());
ifStatement.getIfPart().getCondition().replace(newCondition);
PyStatementList stList = ((PyIfStatement)firstStatement).getIfPart().getStatementList();
PyStatementList ifStatementList = ifStatement.getIfPart().getStatementList();
+
+ List comments = PsiTreeUtil.getChildrenOfTypeAsList(ifStatement.getIfPart(), PsiComment.class);
+ comments.addAll(PsiTreeUtil.getChildrenOfTypeAsList(((PyIfStatement)firstStatement).getIfPart(), PsiComment.class));
+ comments.addAll(PsiTreeUtil.getChildrenOfTypeAsList(ifStatementList, PsiComment.class));
+ comments.addAll(PsiTreeUtil.getChildrenOfTypeAsList(stList, PsiComment.class));
+
+ for (PsiElement comm : comments) {
+ ifStatement.getIfPart().addBefore(comm, ifStatementList);
+ comm.delete();
+ }
ifStatementList.replace(stList);
}
}
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PySplitIfIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PySplitIfIntention.java
index 0dd52f3612c2..aaf3254e7379 100644
--- a/python/src/com/jetbrains/python/codeInsight/intentions/PySplitIfIntention.java
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/PySplitIfIntention.java
@@ -83,10 +83,21 @@ public class PySplitIfIntention extends BaseIntentionAction {
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
PyIfStatement subIf = (PyIfStatement) ifStatement.copy();
+
subIf.getIfPart().getCondition().replace(element.getRightExpression());
ifStatement.getIfPart().getCondition().replace(element.getLeftExpression());
PyStatementList statementList = elementGenerator.createFromText(LanguageLevel.getDefault(), PyIfStatement.class, "if a:\n a = 1").getIfPart().getStatementList();
statementList.getStatements()[0].replace(subIf);
- ifStatement.getIfPart().getStatementList().replace(statementList);
+ PyIfStatement newIf = elementGenerator.createFromText(LanguageLevel.getDefault(), PyIfStatement.class, "if a:\n a = 1");
+ newIf.getIfPart().getCondition().replace(ifStatement.getIfPart().getCondition());
+ newIf.getIfPart().getStatementList().replace(statementList);
+ if (ifStatement.getElifParts() != null) {
+ for (PyIfPart elif : ifStatement.getElifParts())
+ newIf.add(elif);
+ }
+ if (ifStatement.getElsePart() != null) {
+ newIf.add(ifStatement.getElsePart());
+ }
+ ifStatement.replace(newIf);
}
}
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ReplaceListComprehensionWithForIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ReplaceListComprehensionWithForIntention.java
index b55f49fd53cb..b66325e7fd77 100644
--- a/python/src/com/jetbrains/python/codeInsight/intentions/ReplaceListComprehensionWithForIntention.java
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/ReplaceListComprehensionWithForIntention.java
@@ -1,25 +1,17 @@
package com.jetbrains.python.codeInsight.intentions;
import com.intellij.codeInsight.intention.IntentionAction;
-import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyBundle;
-import com.jetbrains.python.PyTokenSeparatorGenerator;
-import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.*;
-import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.impl.PyStatementListImpl;
import org.jetbrains.annotations.NotNull;
-import sun.tools.tree.Statement;
-import javax.swing.*;
import java.util.List;
/**
diff --git a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java
index 695569e87479..0fec303396e4 100644
--- a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java
+++ b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java
@@ -127,7 +127,7 @@ public class PyOverrideImplementUtil {
for (PyMethodMember newMember : newMembers) {
PyFunction baseFunction = (PyFunction) newMember.getPsiElement();
final PyFunctionBuilder builder = buildOverriddenFunction(pyClass, baseFunction);
- PyFunction function = builder.addFunctionAfter(statementList, anchor);
+ PyFunction function = builder.addFunctionAfter(statementList, anchor, LanguageLevel.forElement(statementList));
element = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(function);
}
diff --git a/python/src/com/jetbrains/python/debugger/PyClassTreeChooserDialog.java b/python/src/com/jetbrains/python/debugger/PyClassTreeChooserDialog.java
new file mode 100644
index 000000000000..3bdadd9d57c4
--- /dev/null
+++ b/python/src/com/jetbrains/python/debugger/PyClassTreeChooserDialog.java
@@ -0,0 +1,49 @@
+package com.jetbrains.python.debugger;
+
+import com.google.common.collect.Lists;
+import com.intellij.ide.util.AbstractTreeClassChooserDialog;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.stubs.PyClassNameIndex;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author traff
+ */
+public class PyClassTreeChooserDialog extends AbstractTreeClassChooserDialog{
+ public PyClassTreeChooserDialog(String title,
+ Project project,
+ GlobalSearchScope scope,
+ @Nullable Filter classFilter, @Nullable PyClass initialClass) {
+ super(title, project, scope, classFilter, initialClass);
+ }
+
+ @NotNull
+ @Override
+ protected List getClassesByName(String name,
+ boolean checkBoxState,
+ String pattern,
+ GlobalSearchScope searchScope) {
+ final Collection classes = PyClassNameIndex.find(name, getProject(), searchScope.isSearchInLibraries());
+ final List result = Lists.newArrayList();
+ for (PyClass c: classes) {
+ if (getFilter().isAccepted(c)) {
+ result.add(c);
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ @Nullable
+ protected PyClass getSelectedFromTreeUserObject(DefaultMutableTreeNode node) {
+ return null;
+ }
+}
diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
index 89e8ac6f85a6..94e5c0059742 100644
--- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
+++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
@@ -8,6 +8,7 @@ import com.intellij.execution.process.ProcessListener;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.execution.ui.ExecutionConsole;
+import com.intellij.openapi.application.ApplicationInfo;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
@@ -16,6 +17,7 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Key;
import com.intellij.xdebugger.XDebugProcess;
import com.intellij.xdebugger.XDebugSession;
+import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.XBreakpointHandler;
@@ -41,8 +43,6 @@ import static javax.swing.SwingUtilities.invokeLater;
// todo: pydevd supports module reloading - look for a way to use the feature
// todo: smart step into
public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, ProcessListener {
-
- private final PyPositionConverter myPositionConverter;
private final RemoteDebugger myDebugger;
private final XBreakpointHandler[] myBreakpointHandlers;
private final PyDebuggerEditorsProvider myEditorsProvider;
@@ -58,18 +58,12 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
private boolean myClosing = false;
- public PyDebugProcess(@NotNull XDebugSession session,
+ private PyPositionConverter myPositionConverter;
+
+ public PyDebugProcess(final @NotNull XDebugSession session,
@NotNull final ServerSocket serverSocket,
@NotNull final ExecutionConsole executionConsole,
@Nullable final ProcessHandler processHandler) {
- this(session, serverSocket, executionConsole, processHandler, new PyLocalPositionConverter());
- }
-
-
- public PyDebugProcess(@NotNull XDebugSession session,
- @NotNull final ServerSocket serverSocket,
- @NotNull final ExecutionConsole executionConsole,
- @Nullable final ProcessHandler processHandler, @NotNull PyPositionConverter positionConverter) {
super(session);
session.setPauseActionSupported(true);
myDebugger = new RemoteDebugger(this, serverSocket, 10);
@@ -80,9 +74,20 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
if (myProcessHandler != null) {
myProcessHandler.addProcessListener(this);
}
+ myPositionConverter = new PyLocalPositionConverter();
+ myDebugger.addCloseListener(new RemoteDebuggerCloseListener() {
+ @Override
+ public void closed() {
+ session.stop();
+ }
+ });
+ }
+
+ public void setPositionConverter(PyPositionConverter positionConverter) {
myPositionConverter = positionConverter;
}
+
@Override
public PyPositionConverter getPositionConverter() {
return myPositionConverter;
@@ -120,6 +125,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
try {
myDebugger.waitForConnect();
handshake();
+ getSession().rebuildViews();
registerBreakpoints();
new RunCommand(myDebugger).execute();
}
@@ -138,8 +144,19 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
}
private void handshake() throws PyDebuggerException {
- final String remoteVersion = myDebugger.handshake();
- printToConsole("Connected to pydevd (version " + remoteVersion + ")\n", ConsoleViewContentType.SYSTEM_OUTPUT);
+ String remoteVersion = myDebugger.handshake();
+ String currentBuild = ApplicationInfo.getInstance().getBuild().asStringWithoutProductCode();
+ if ("@@BUILD_NUMBER@@".equals(remoteVersion)) {
+ remoteVersion = currentBuild;
+ }
+ if (remoteVersion.startsWith("PY-")) {
+ remoteVersion = remoteVersion.substring(3);
+ }
+ printToConsole("Connected to pydev debugger (build " + remoteVersion + ")\n", ConsoleViewContentType.SYSTEM_OUTPUT);
+
+ if (!remoteVersion.equals(currentBuild)) {
+ printToConsole("Warning: wrong debugger version. Use pycharm-debugger.egg from PyCharm installation folder.\n", ConsoleViewContentType.ERROR_OUTPUT);
+ }
}
public void printToConsole(String text, ConsoleViewContentType contentType) {
@@ -147,14 +164,22 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
}
private void registerBreakpoints() {
- for (Map.Entry entry : myRegisteredBreakpoints.entrySet()) {
- addBreakpoint(entry.getKey(), entry.getValue());
- }
+ registerLineBreakpoints();
+ registerExceptionBreakpoints();
+ }
+
+ private void registerExceptionBreakpoints() {
for (XBreakpoint bp : myRegisteredExceptionBreakpoints.values()) {
addExceptionBreakpoint(bp);
}
}
+ public void registerLineBreakpoints() {
+ for (Map.Entry entry : myRegisteredBreakpoints.entrySet()) {
+ addBreakpoint(entry.getKey(), entry.getValue());
+ }
+ }
+
@Override
public void startStepOver() {
resume(ResumeCommand.Mode.STEP_OVER);
@@ -266,6 +291,11 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
myNewVariableValue.put(frame.getThreadFrameId(), newValue);
}
+ @Nullable
+ public String loadSource(String path) {
+ return myDebugger.loadSource(path);
+ }
+
@Override
public boolean isVariable(String name) {
final Project project = getSession().getProject();
@@ -405,4 +435,21 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
@Override
public void onTextAvailable(ProcessEvent event, Key outputType) {
}
+
+ public PyStackFrame createStackFrame(PyStackFrameInfo frameInfo) {
+ return new PyStackFrame(this, frameInfo);
+ }
+
+ @Override
+ public String getCurrentStateMessage() {
+ if (getSession().isStopped()) {
+ return XDebuggerBundle.message("debugger.state.message.disconnected");
+ }
+ else if (myDebugger.isConnected()) {
+ return XDebuggerBundle.message("debugger.state.message.connected");
+ }
+ else {
+ return "Waiting for connection...";
+ }
+ }
}
diff --git a/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointType.java b/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointType.java
index f2c723422c2b..9f69846281fe 100644
--- a/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointType.java
+++ b/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointType.java
@@ -12,7 +12,6 @@ import com.intellij.xdebugger.XDebuggerManager;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.XBreakpointType;
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel;
-import com.jetbrains.python.debugger.ui.PyClassTreeChooserDialog;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyUtil;
import org.jetbrains.annotations.NotNull;
diff --git a/python/src/com/jetbrains/python/debugger/PyExecutionStack.java b/python/src/com/jetbrains/python/debugger/PyExecutionStack.java
index 599183114d9a..c8da6847ef84 100644
--- a/python/src/com/jetbrains/python/debugger/PyExecutionStack.java
+++ b/python/src/com/jetbrains/python/debugger/PyExecutionStack.java
@@ -53,7 +53,7 @@ public class PyExecutionStack extends XExecutionStack {
}
private static PyStackFrame convert(final PyDebugProcess debugProcess, final PyStackFrameInfo frameInfo) {
- return new PyStackFrame(debugProcess, frameInfo);
+ return debugProcess.createStackFrame(frameInfo);
}
@Override
diff --git a/python/src/com/jetbrains/python/debugger/PyLocalPositionConverter.java b/python/src/com/jetbrains/python/debugger/PyLocalPositionConverter.java
index d46b79b3f631..8707120cd245 100644
--- a/python/src/com/jetbrains/python/debugger/PyLocalPositionConverter.java
+++ b/python/src/com/jetbrains/python/debugger/PyLocalPositionConverter.java
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.Nullable;
public class PyLocalPositionConverter implements PyPositionConverter {
protected static class PyLocalSourcePosition extends PySourcePosition {
- PyLocalSourcePosition(final String file, final int line) {
+ public PyLocalSourcePosition(final String file, final int line) {
super(file, line);
}
}
diff --git a/python/src/com/jetbrains/python/debugger/PyRemotePositionConverter.java b/python/src/com/jetbrains/python/debugger/PyRemotePositionConverter.java
deleted file mode 100644
index 58b02ffb66e8..000000000000
--- a/python/src/com/jetbrains/python/debugger/PyRemotePositionConverter.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.jetbrains.python.debugger;
-
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.xdebugger.XSourcePosition;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * @author traff
- */
-public class PyRemotePositionConverter extends PyLocalPositionConverter {
- private final String myLocalRoot;
- private final String myRemoteRoot;
-
- public PyRemotePositionConverter(final String localRoot, final String remoteRoot) {
- myLocalRoot = FileUtil.toSystemIndependentName(localRoot);
- myRemoteRoot = FileUtil.toSystemIndependentName(remoteRoot);
- }
-
- @NotNull
- @Override
- public PySourcePosition convert(@NotNull XSourcePosition position) {
- String path = FileUtil.toSystemIndependentName(position.getFile().getPath());
- if (myLocalRoot.length() > 0) {
- path = path.replace(myLocalRoot, myRemoteRoot);
- }
- return new PyLocalSourcePosition(path, position.getLine() + 1);
- }
-
- @Override
- public XSourcePosition convert(@NotNull PySourcePosition position) {
- String path = FileUtil.toSystemIndependentName(position.getFile());
- if (myRemoteRoot.length() > 0) {
- path = path.replace(myRemoteRoot, myLocalRoot);
- }
- final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path);
- return createXSourcePosition(file, position.getLine());
- }
-}
diff --git a/python/src/com/jetbrains/python/debugger/PyStackFrame.java b/python/src/com/jetbrains/python/debugger/PyStackFrame.java
index 91554bf7f0f3..bb1847072911 100644
--- a/python/src/com/jetbrains/python/debugger/PyStackFrame.java
+++ b/python/src/com/jetbrains/python/debugger/PyStackFrame.java
@@ -25,7 +25,7 @@ public class PyStackFrame extends XStackFrame {
private final PyDebugProcess myDebugProcess;
private final PyStackFrameInfo myFrameInfo;
- private final XSourcePosition myPosition;
+ private XSourcePosition myPosition;
public PyStackFrame(@NotNull final PyDebugProcess debugProcess, @NotNull final PyStackFrameInfo frameInfo) {
myDebugProcess = debugProcess;
@@ -110,4 +110,8 @@ public class PyStackFrame extends XStackFrame {
return myFrameInfo.getThreadId() + ":" + myFrameInfo.getId();
}
+ protected XSourcePosition getPosition() {
+ return myPosition;
+ }
+
}
diff --git a/python/src/com/jetbrains/python/editor/selectWord/PyCommaSelectionHandler.java b/python/src/com/jetbrains/python/editor/selectWord/PyCommaSelectionHandler.java
index 4df752b5883b..3b6da03e0089 100644
--- a/python/src/com/jetbrains/python/editor/selectWord/PyCommaSelectionHandler.java
+++ b/python/src/com/jetbrains/python/editor/selectWord/PyCommaSelectionHandler.java
@@ -21,7 +21,8 @@ import java.util.List;
public class PyCommaSelectionHandler implements ExtendWordSelectionHandler {
public boolean canSelect(final PsiElement e) {
return e instanceof PyReferenceExpression || e instanceof PyKeyValueExpression || e instanceof PyKeywordArgument
- || e instanceof PyNumericLiteralExpression || e instanceof PyStringLiteralExpression;
+ || e instanceof PyNumericLiteralExpression || e instanceof PyStringLiteralExpression || e instanceof PyNamedParameter
+ || e instanceof PyStarArgument;
}
@Override
diff --git a/python/src/com/jetbrains/python/editor/selectWord/PyListSelectionHandler.java b/python/src/com/jetbrains/python/editor/selectWord/PyListSelectionHandler.java
index 92a13dffa4a3..53ae223f6eb7 100644
--- a/python/src/com/jetbrains/python/editor/selectWord/PyListSelectionHandler.java
+++ b/python/src/com/jetbrains/python/editor/selectWord/PyListSelectionHandler.java
@@ -4,7 +4,10 @@ import com.intellij.codeInsight.editorActions.ExtendWordSelectionHandler;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiWhiteSpace;
+import com.jetbrains.python.psi.PyArgumentList;
import com.jetbrains.python.psi.PyListLiteralExpression;
+import com.jetbrains.python.psi.PyParameterList;
import java.util.Collections;
import java.util.List;
@@ -17,13 +20,22 @@ import java.util.List;
public class PyListSelectionHandler implements ExtendWordSelectionHandler {
@Override
public boolean canSelect(PsiElement e) {
- return e instanceof PyListLiteralExpression;
+ return e instanceof PyListLiteralExpression || e instanceof PyParameterList || e instanceof PyArgumentList;
}
@Override
public List select(PsiElement e, CharSequence editorText, int cursorOffset, Editor editor) {
TextRange stringRange = e.getTextRange();
- TextRange offsetRange = new TextRange(stringRange.getStartOffset() + 1, stringRange.getEndOffset() -1 );
+ PsiElement firstChild = e.getFirstChild().getNextSibling();
+ int startShift = 1;
+ if (firstChild instanceof PsiWhiteSpace)
+ startShift += firstChild.getTextLength();
+ PsiElement lastChild = e.getLastChild().getPrevSibling();
+ int endShift = 1;
+ if (lastChild instanceof PsiWhiteSpace)
+ endShift += lastChild.getTextLength();
+
+ TextRange offsetRange = new TextRange(stringRange.getStartOffset() + startShift, stringRange.getEndOffset() - endShift );
if (offsetRange.contains(cursorOffset) && offsetRange.getLength() > 1) {
return Collections.singletonList(offsetRange);
}
diff --git a/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java b/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java
index 69563a657c72..5a7383a1d72e 100644
--- a/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java
@@ -2,11 +2,12 @@ package com.jetbrains.python.inspections;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.PsiElementVisitor;
+import com.intellij.psi.tree.TokenSet;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.actions.AugmentedAssignmentQuickFix;
-import com.jetbrains.python.actions.RedundantParenthesesQuickFix;
import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -37,23 +38,39 @@ public class PyAugmentAssignmentInspection extends PyInspection {
@Override
public void visitPyAssignmentStatement(final PyAssignmentStatement node) {
- if (node.getLeftHandSideExpression() instanceof PyTargetExpression &&
- node.getAssignedValue() instanceof PyBinaryExpression) {
- PyTargetExpression target = ((PyTargetExpression)node.getLeftHandSideExpression());
+ if (node.getAssignedValue() instanceof PyBinaryExpression) {
+ PyExpression target = node.getLeftHandSideExpression();
PyBinaryExpression expression = (PyBinaryExpression)node.getAssignedValue();
PyExpression leftExpression = expression.getLeftExpression();
PyExpression rightExpression = expression.getRightExpression();
- if (PyTokenTypes.ADDITIVE_OPERATIONS.contains(expression.getOperator()) ||
- PyTokenTypes.MULTIPLICATIVE_OPERATIONS.contains(expression.getOperator())) {
- if (leftExpression != null && leftExpression instanceof PyReferenceExpression) {
- if (leftExpression.getText().equals(target.getText())) {
- if (rightExpression instanceof PyNumericLiteralExpression) {
- PyElementType op = expression.getOperator();
- if (op == PyTokenTypes.PLUS || op == PyTokenTypes.MINUS ||
- op == PyTokenTypes.MULT || op == PyTokenTypes.DIV) {
+ if (rightExpression != null) {
+ boolean changedParts = false;
+ if (rightExpression.getText().equals(target.getText())) {
+ PyExpression tmp = rightExpression;
+ rightExpression = leftExpression;
+ leftExpression = tmp;
+ changedParts = true;
+ }
+ PyElementType op = expression.getOperator();
+ final TokenSet operations = TokenSet.create(PyTokenTypes.PLUS, PyTokenTypes.MINUS, PyTokenTypes.MULT,
+ PyTokenTypes.FLOORDIV, PyTokenTypes.DIV, PyTokenTypes.PERC, PyTokenTypes.AND, PyTokenTypes.OR,
+ PyTokenTypes.XOR, PyTokenTypes.LTLT, PyTokenTypes.GTGT, PyTokenTypes.EXP);
+ final TokenSet commutativeOperations = TokenSet.create(PyTokenTypes.PLUS, PyTokenTypes.MULT);
+ if ((operations.contains(op) && !changedParts) || (changedParts && commutativeOperations.contains(op))) {
+ if (leftExpression != null
+ && (leftExpression instanceof PyReferenceExpression || leftExpression instanceof PySubscriptionExpression)) {
+ if (leftExpression.getText().equals(target.getText())) {
+ if (rightExpression instanceof PyNumericLiteralExpression) {
AugmentedAssignmentQuickFix quickFix = new AugmentedAssignmentQuickFix();
registerProblem(node, "Assignment can be replaced with augmented assignment", quickFix);
}
+ else {
+ PyType type = rightExpression.getType(myTypeEvalContext);
+ if (type != null) {
+ if (type.getName().equals("int") || type.getName().equals("str"))
+ registerProblem(node, "Assignment can be replaced with augmented assignment", new AugmentedAssignmentQuickFix());
+ }
+ }
}
}
}
diff --git a/python/src/com/jetbrains/python/inspections/PyChainedComparisonsInspection.java b/python/src/com/jetbrains/python/inspections/PyChainedComparisonsInspection.java
index f10092a059f2..6cdeb12f12c3 100644
--- a/python/src/com/jetbrains/python/inspections/PyChainedComparisonsInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyChainedComparisonsInspection.java
@@ -55,7 +55,7 @@ public class PyChainedComparisonsInspection extends PyInspection {
return true;
}
- if (leftExpression.getOperator() == rightExpression.getOperator() &&
+ if (/*leftExpression.getOperator() == rightExpression.getOperator() && */
PyTokenTypes.RELATIONAL_OPERATIONS.contains(leftExpression.getOperator())) {
PyExpression leftRight = leftExpression.getRightExpression();
if (leftRight != null) {
diff --git a/python/src/com/jetbrains/python/inspections/PyDictDuplicateKeysInspection.java b/python/src/com/jetbrains/python/inspections/PyDictDuplicateKeysInspection.java
index 56e90deea0df..4fefc4b47fc8 100644
--- a/python/src/com/jetbrains/python/inspections/PyDictDuplicateKeysInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyDictDuplicateKeysInspection.java
@@ -1,20 +1,15 @@
package com.jetbrains.python.inspections;
import com.intellij.codeInspection.ProblemsHolder;
-import com.intellij.openapi.util.Pair;
-import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
-import com.intellij.psi.PsiPolyVariantReference;
-import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.PyBundle;
-import com.jetbrains.python.actions.DictCreationQuickFix;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Set;
/**
* User: catherine
@@ -57,5 +52,65 @@ public class PyDictDuplicateKeysInspection extends PyInspection {
}
}
}
+
+ @Override
+ public void visitPyCallExpression(PyCallExpression node) {
+ if (isDict(node)) {
+ HashSet set = new HashSet();
+ PyExpression[] argumentList = node.getArgumentList().getArguments();
+ for (PyExpression argument : argumentList) {
+ if (argument instanceof PyParenthesizedExpression)
+ argument = ((PyParenthesizedExpression)argument).getContainedExpression();
+ if (argument instanceof PySequenceExpression) {
+ for (PyElement el : ((PySequenceExpression)argument).getElements()) {
+ String key = getKey(el);
+ checkKey(key, set, node);
+ }
+ }
+ else {
+ String key = getKey(argument);
+ checkKey(key, set, node);
+ }
+ }
+ }
+ }
+
+ private void checkKey(String key, Set set, PyCallExpression node) {
+ if (key != null) {
+ if (set.contains(key)) {
+ registerProblem(node, "Dictionary contains duplicate keys " + key);
+ }
+ set.add(key);
+ }
+ }
+
+ @Nullable
+ private String getKey(PyElement argument) {
+ if (argument instanceof PyParenthesizedExpression) {
+ PyExpression expr = ((PyParenthesizedExpression)argument).getContainedExpression();
+ if (expr instanceof PyTupleExpression) {
+ PyElement key = ((PyTupleExpression)expr).getElements()[0];
+ if (key instanceof PyStringLiteralExpression) {
+ return ((PyStringLiteralExpression)key).getStringValue();
+ }
+ else {
+ return key.getText();
+ }
+ }
+ }
+ if (argument instanceof PyKeywordArgument) {
+ return ((PyKeywordArgument)argument).getKeyword();
+ }
+ return null;
+ }
+
+ private boolean isDict(PyCallExpression expression) {
+ String name = expression.getCallee().getText();
+ if ("dict".equals(name)) {
+ return true;
+ }
+ return false;
+ }
+
}
}
diff --git a/python/src/com/jetbrains/python/inspections/PyRedundantParenthesesInspection.java b/python/src/com/jetbrains/python/inspections/PyRedundantParenthesesInspection.java
index dfc17642f609..8a4a92dad54b 100644
--- a/python/src/com/jetbrains/python/inspections/PyRedundantParenthesesInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyRedundantParenthesesInspection.java
@@ -1,11 +1,9 @@
package com.jetbrains.python.inspections;
-import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
-import com.intellij.psi.PsiWhiteSpace;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.actions.RedundantParenthesesQuickFix;
@@ -45,8 +43,7 @@ public class PyRedundantParenthesesInspection extends PyInspection {
@Override
public void visitPyParenthesizedExpression(final PyParenthesizedExpression node) {
- if (node.getContainedExpression() instanceof PyReferenceExpression ||
- node.getContainedExpression() instanceof PyStringLiteralExpression
+ if (node.getContainedExpression() instanceof PyReferenceExpression
|| node.getContainedExpression() instanceof PyNumericLiteralExpression) {
if (myIgnorePercOperator) {
PsiElement parent = node.getParent();
@@ -55,7 +52,7 @@ public class PyRedundantParenthesesInspection extends PyInspection {
}
}
- if (node.getParent() instanceof PyPrintStatement && ((PyFile)node.getContainingFile()).getLanguageLevel().isPy3K())
+ if (node.getParent() instanceof PyPrintStatement)
return;
registerProblem(node, "Remove redundant parentheses", new RedundantParenthesesQuickFix());
}
diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java
index cc075cb2e953..4e503a18f2a1 100644
--- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java
@@ -9,6 +9,7 @@ import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.JDOMExternalizableStringList;
import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.io.FileUtil;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
@@ -124,8 +125,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
super.visitPyImportElement(node);
final PyFromImportStatement fromImport = PsiTreeUtil.getParentOfType(node, PyFromImportStatement.class);
PsiFile file = node.getContainingFile();
- String fileNameWithExt = file.getName();
- String fileName = fileNameWithExt.substring(0, fileNameWithExt.length() - 3);
+ String fileName = FileUtil.getNameWithoutExtension(file.getName());
if (fromImport == null && fileName.equals(node.getText())) {
registerProblem(node, "Import resolves to its containing file.");
}
@@ -209,6 +209,27 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
actions.add(new UnresolvedReferenceAddSelfQuickFix(refex));
}
}
+ for (PyStatement statement : containedClass.getStatementList().getStatements()) {
+ if (statement instanceof PyAssignmentStatement) {
+ if (((PyAssignmentStatement)statement).getLeftHandSideExpression().getText().equals(refex.getText())) {
+ PyExpression callexpr = ((PyAssignmentStatement)statement).getAssignedValue();
+ if (callexpr instanceof PyCallExpression) {
+ PyType type = myTypeEvalContext.getType(callexpr);
+ if (type != null && type instanceof PyClassType) {
+ String name = ((PyCallExpression)callexpr).getCallee().getText();
+ if (name != null && name.equals("property"))
+ actions.add(new UnresolvedReferenceAddSelfQuickFix(refex));
+ }
+ }
+ }
+ }
+ }
+ for (PyFunction method : containedClass.getMethods()) {
+ Property property = method.getProperty();
+ if (property != null && method.getName().equals(refex.getText())) {
+ actions.add(new UnresolvedReferenceAddSelfQuickFix(refex));
+ }
+ }
}
PyCallExpression callExpression = PsiTreeUtil.getParentOfType(ref_element, PyCallExpression.class);
if (callExpression != null)
diff --git a/python/src/com/jetbrains/python/psi/PyAssignmentStatement.java b/python/src/com/jetbrains/python/psi/PyAssignmentStatement.java
index 6613d7a404d1..4a6977bc82b1 100644
--- a/python/src/com/jetbrains/python/psi/PyAssignmentStatement.java
+++ b/python/src/com/jetbrains/python/psi/PyAssignmentStatement.java
@@ -16,6 +16,15 @@ public interface PyAssignmentStatement extends PyStatement, NameDefiner {
*/
PyExpression[] getTargets();
+ /**
+ * Return all expressions which are considered assignment targets (to the left of the last = sign in the statement).
+ * Doesn't unpack tuples, parentheses or anything.
+ *
+ * @return the list of assignment target expressions
+ */
+ @NotNull
+ PyExpression[] getRawTargets();
+
/**
* @return right-hand side of the statement; may as well consist of many elements.
diff --git a/python/src/com/jetbrains/python/psi/impl/PropertyBunch.java b/python/src/com/jetbrains/python/psi/impl/PropertyBunch.java
index 60d854694b64..1ef347def7ea 100644
--- a/python/src/com/jetbrains/python/psi/impl/PropertyBunch.java
+++ b/python/src/com/jetbrains/python/psi/impl/PropertyBunch.java
@@ -116,6 +116,9 @@ public abstract class PropertyBunch {
if (arg instanceof PyKeywordArgument) {
String keyword = ((PyKeywordArgument)arg).getKeyword();
index = ArrayUtil.indexOf(keywords, keyword);
+ if (index < 0) {
+ continue;
+ }
position = -1;
}
else if (position >= 0) {
diff --git a/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java
index 4bffb53f35a4..93c431612cf0 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java
@@ -36,12 +36,18 @@ public class PyAssignmentStatementImpl extends PyElementImpl implements PyAssign
public PyExpression[] getTargets() {
if (myTargets == null) {
- myTargets = calcTargets();
+ myTargets = calcTargets(false);
}
return myTargets;
}
- private PyExpression[] calcTargets() {
+ @NotNull
+ @Override
+ public PyExpression[] getRawTargets() {
+ return calcTargets(true);
+ }
+
+ private PyExpression[] calcTargets(boolean raw) {
final ASTNode[] eqSigns = getNode().getChildren(TokenSet.create(PyTokenTypes.EQ));
if (eqSigns.length == 0) {
return PyExpression.EMPTY_ARRAY;
@@ -52,13 +58,19 @@ public class PyAssignmentStatementImpl extends PyElementImpl implements PyAssign
while (node != null && node != lastEq) {
final PsiElement psi = node.getPsi();
if (psi instanceof PyExpression) {
- addCandidate(candidates, (PyExpression)psi);
+ if (raw) {
+ candidates.add((PyExpression) psi);
+ }
+ else {
+ addCandidate(candidates, (PyExpression)psi);
+ }
}
node = node.getTreeNext();
}
List targets = new ArrayList();
for (PyExpression expr : candidates) { // only filter out targets
- if (expr instanceof PyTargetExpression ||
+ if (raw ||
+ expr instanceof PyTargetExpression ||
expr instanceof PyReferenceExpression ||
expr instanceof PySubscriptionExpression ||
expr instanceof PySliceExpression) {
diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
index 872022dc5fe0..1c8e51caae32 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
@@ -246,11 +246,20 @@ public class PyCallExpressionHelper {
PyType qtype = context.getType(lastQualifier);
if (qtype != null) {
if (qtype instanceof PyClassType) {
- return ! ((PyClassType)qtype).isDefinition();
+ if (!((PyClassType)qtype).isDefinition()) {
+ return false;
+ }
+ PyClass resolvedParent = PsiTreeUtil.getParentOfType(resolved, PyClass.class);
+ if (resolvedParent != null) {
+ final PyClass qualifierClass = ((PyClassType)qtype).getPyClass();
+ if (qualifierClass != null && (qualifierClass.isSubclass(resolvedParent) || resolvedParent.isSubclass(qualifierClass))) {
+ return false;
+ }
+ }
}
- else return true; // TODO: handle UnionType
+ // TODO: handle UnionType
}
- else return true; // NOTE. best guess: unknown qualifier is more probably an instance.
+ return true; // NOTE. best guess: unknown qualifier is more probably an instance.
}
return false;
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
index 84a4d0aaae7f..bd7b96dd3a99 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
@@ -71,7 +71,7 @@ public class PyDecoratorImpl extends PyPresentableElementImpl i
List parts = PyResolveUtil.unwindQualifiers(node);
if (parts != null) {
//Collections.reverse(parts);
- return new PyQualifiedName(parts);
+ return PyQualifiedName.fromReferenceChain(parts);
}
}
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 c3006dac915a..ea1a4238ae19 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java
@@ -416,6 +416,13 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
}
}
+ @Override
+ public void visitPyAugAssignmentStatement(PyAugAssignmentStatement node) {
+ if (PyNames.ALL.equals(node.getTarget().getName())) {
+ myDynamic = true;
+ }
+ }
+
@Override
public void visitPyCallExpression(PyCallExpression node) {
final PyExpression callee = node.getCallee();
diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java
index 6c608097c7dd..a4625ec39fab 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java
@@ -43,18 +43,18 @@ public class PyFunctionBuilder {
return this;
}
- public PyFunction addFunction(PsiElement target) {
- return (PyFunction) target.add(buildFunction(target.getProject()));
+ public PyFunction addFunction(PsiElement target, final LanguageLevel languageLevel) {
+ return (PyFunction) target.add(buildFunction(target.getProject(), languageLevel));
}
- public PyFunction addFunctionAfter(PsiElement target, PsiElement anchor) {
- return (PyFunction) target.addAfter(buildFunction(target.getProject()), anchor);
+ public PyFunction addFunctionAfter(PsiElement target, PsiElement anchor, final LanguageLevel languageLevel) {
+ return (PyFunction) target.addAfter(buildFunction(target.getProject(), languageLevel), anchor);
}
- public PyFunction buildFunction(Project project) {
+ public PyFunction buildFunction(Project project, final LanguageLevel languageLevel) {
String text = buildText(project);
PyElementGenerator generator = PyElementGenerator.getInstance(project);
- return generator.createFromText(LanguageLevel.getDefault(), PyFunction.class, text);
+ return generator.createFromText(languageLevel, PyFunction.class, text);
}
private String buildText(Project project) {
diff --git a/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java
index d1cf2ac0b35d..4b8ba23c1d96 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java
@@ -177,7 +177,7 @@ public class PyImportReferenceImpl extends PyReferenceImpl {
if (thisQName == null) {
fillFromDir(ResolveImportUtil.stepBackFrom(myCurrentFile, relative_level), insertHandler);
}
- else {
+ else if (thisQName.getComponentCount() >= relative_level) {
thisQName = thisQName.removeTail(relative_level);
fillFromQName(thisQName, insertHandler);
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyQualifiedName.java b/python/src/com/jetbrains/python/psi/impl/PyQualifiedName.java
index 27cb5772bf21..1fb31e050083 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyQualifiedName.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyQualifiedName.java
@@ -25,11 +25,17 @@ public class PyQualifiedName {
myComponents = new ArrayList(count);
}
- public PyQualifiedName(List components) {
- myComponents = new ArrayList(components.size());
+ @Nullable
+ public static PyQualifiedName fromReferenceChain(List components) {
+ PyQualifiedName qName = new PyQualifiedName(components.size());
for (PyReferenceExpression component : components) {
- myComponents.add(component.getReferencedName());
+ final String refName = component.getReferencedName();
+ if (refName == null) {
+ return null;
+ }
+ qName.myComponents.add(refName);
}
+ return qName;
}
public static PyQualifiedName fromComponents(Collection components) {
diff --git a/python/src/com/jetbrains/python/psi/impl/PyQualifiedReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/PyQualifiedReferenceImpl.java
index 718c2822f304..d5fa7229671a 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyQualifiedReferenceImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyQualifiedReferenceImpl.java
@@ -60,13 +60,12 @@ public class PyQualifiedReferenceImpl extends PyReferenceImpl {
if (ref_elt != null) ret.poke(ref_elt, RatedResolveResult.RATE_NORMAL);
// NOTE: maybe rate non-first results lower.
}
- // enrich the type info with any fields assigned nearby
- if (qualifier instanceof PyQualifiedExpression) {
- // enrich the type info with any fields assigned nearby
- if (addAssignedAttributes(ret, referencedName, qualifier)) return ret;
- }
// resolve within the type proper
addResolveMember(ret, referencedName, qualifierType, ctx);
+ // enrich the type info with any fields assigned nearby
+ if (qualifier instanceof PyQualifiedExpression && !ret.isEmpty()) {
+ if (addAssignedAttributes(ret, referencedName, qualifier)) return ret;
+ }
}
else if (myContext.allowImplicits()) {
final Collection functions = PyFunctionNameIndex.find(referencedName, myElement.getProject());
diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
index ffcb0d4b04f7..d7c86ae0461e 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
@@ -148,7 +148,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
if (components == null) {
return null;
}
- return new PyQualifiedName(components);
+ return PyQualifiedName.fromReferenceChain(components);
}
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java
index 22f93d53ceea..72aceb2ba863 100644
--- a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java
+++ b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java
@@ -410,8 +410,24 @@ public class ResolveImportUtil {
if (elt_psifile != null) { // formality
final VirtualFile elt_vfile = elt_psifile.getVirtualFile();
if (elt_vfile != null) { // reality
- for (OrderEntry entry : ProjectRootManager.getInstance(elt.getProject()).getFileIndex().getOrderEntriesForFile(elt_vfile)) {
- if (!visitOrderEntryRoots(visitor, entry)) break;
+ final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(elt.getProject()).getFileIndex();
+ final List orderEntries = fileIndex.getOrderEntriesForFile(elt_vfile);
+ if (orderEntries.size() > 0) {
+ for (OrderEntry entry : orderEntries) {
+ if (!visitOrderEntryRoots(visitor, entry)) break;
+ }
+ }
+ else {
+ // out-of-project file - use roots of SDK assigned to project
+ final Sdk sdk = PyBuiltinCache.findSdkForFile(elt_psifile);
+ if (sdk != null) {
+ final VirtualFile[] roots = sdk.getRootProvider().getFiles(OrderRootType.CLASSES);
+ for (VirtualFile root : roots) {
+ if (!visitor.visitRoot(root)) {
+ break;
+ }
+ }
+ }
}
}
}
diff --git a/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java b/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java
index 04daea9e6c15..2f94ccbd05ff 100644
--- a/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java
+++ b/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java
@@ -270,7 +270,7 @@ public class PyExtractMethodUtil {
builder.parameter(data.getName());
}
}
- final PyParameterList pyParameterList = builder.buildFunction(project).getParameterList();
+ final PyParameterList pyParameterList = builder.buildFunction(project, LanguageLevel.getDefault()).getParameterList();
generatedMethod.getParameterList().replace(pyParameterList);
}
@@ -316,7 +316,7 @@ public class PyExtractMethodUtil {
final PyFunctionBuilder builder = new PyFunctionBuilder(methodName);
addFakeParameters(builder, variableData);
builder.statement("return " + expression.getText());
- return builder.buildFunction(project);
+ return builder.buildFunction(project, LanguageLevel.getDefault());
}
private static PyFunction generateMethodFromElements(final Project project,
@@ -327,7 +327,7 @@ public class PyExtractMethodUtil {
final PyFunctionBuilder builder = new PyFunctionBuilder(methodName);
addFakeParameters(builder, variableData);
- final PyFunction method = builder.buildFunction(project);
+ final PyFunction method = builder.buildFunction(project, LanguageLevel.getDefault());
final PyStatementList statementList = method.getStatementList();
for (PsiElement element : elementsRange) {
diff --git a/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java
index e0e7a102ab69..a4c244cdd780 100644
--- a/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java
+++ b/python/src/com/jetbrains/python/refactoring/introduce/field/FieldIntroduceHandler.java
@@ -105,7 +105,7 @@ public class FieldIntroduceHandler extends IntroduceHandler {
}
final PyFunctionBuilder builder = new PyFunctionBuilder(PythonUnitTestUtil.TESTCASE_SETUP_NAME);
builder.parameter(PyNames.CANONICAL_SELF);
- PyFunction setUp = builder.buildFunction(project);
+ PyFunction setUp = builder.buildFunction(project, LanguageLevel.getDefault());
final PyStatementList statements = clazz.getStatementList();
final PsiElement anchor = statements.getFirstChild();
setUp = (PyFunction)statements.addBefore(setUp, anchor);
diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java
index 25d558abab00..e822f6f1feaa 100644
--- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java
+++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java
@@ -707,7 +707,7 @@ public class PythonSdkType extends SdkType {
}
@Nullable
- public static Sdk findPythonSdk(Module module) {
+ public static Sdk findPythonSdk(@Nullable Module module) {
if (module == null) return null;
final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) return sdk;
diff --git a/python/src/com/jetbrains/python/testing/PythonTestConfigurationsModel.java b/python/src/com/jetbrains/python/testing/PythonTestConfigurationsModel.java
new file mode 100644
index 000000000000..d0552c0d3758
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/PythonTestConfigurationsModel.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2000-2009 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.testing;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.ui.CollectionComboBoxModel;
+import com.jetbrains.python.PyBundle;
+
+import java.util.List;
+
+/**
+ * User: catherine
+ */
+
+public class PythonTestConfigurationsModel extends CollectionComboBoxModel {
+ public static final String PYTHONS_UNITTEST_NAME = PyBundle.message("runcfg.unittest.display_name");
+ public static final String PYTHONS_NOSETEST_NAME = PyBundle.message("runcfg.nosetests.display_name");
+ public static final String PY_TEST_NAME = PyBundle.message("runcfg.pytest.display_name");
+
+ private String myProjectConfiguration;
+ private Project myProject;
+
+ public PythonTestConfigurationsModel(final List items, final Object selection, Project project) {
+ super(items, selection);
+ myProject = project;
+ myProjectConfiguration = TestRunnerService.getInstance(myProject).getProjectConfiguration();
+ }
+ public void reset() {
+ setSelectedItem(myProjectConfiguration);
+ }
+
+ public void apply() {
+ myProjectConfiguration = (String)getSelectedItem();
+ TestRunnerService.getInstance(myProject).setProjectConfiguration(myProjectConfiguration);
+ }
+
+ public Object getProjectConfiguration() {
+ return myProjectConfiguration;
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/PythonUnitTestCommandLineState.java b/python/src/com/jetbrains/python/testing/PythonUnitTestCommandLineState.java
index 72d2592e8a3a..02f5c5c239b4 100644
--- a/python/src/com/jetbrains/python/testing/PythonUnitTestCommandLineState.java
+++ b/python/src/com/jetbrains/python/testing/PythonUnitTestCommandLineState.java
@@ -57,7 +57,7 @@ public class PythonUnitTestCommandLineState extends PythonTestCommandLineStateBa
break;
case TEST_FOLDER:
if (!myConfig.getPattern().isEmpty())
- specs.add(myConfig.getFolderName() + "/" + ";" + myConfig.getPattern() + "$");
+ specs.add(myConfig.getFolderName() + "/" + ";" + myConfig.getPattern());
else
specs.add(myConfig.getFolderName() + "/");
// TODO[kate]:think about delimiter between folderName and Pattern
diff --git a/python/src/com/jetbrains/python/testing/PythonUnitTestConfigurationProducer.java b/python/src/com/jetbrains/python/testing/PythonUnitTestConfigurationProducer.java
index f48b4bd1bdef..53e0ac215fea 100644
--- a/python/src/com/jetbrains/python/testing/PythonUnitTestConfigurationProducer.java
+++ b/python/src/com/jetbrains/python/testing/PythonUnitTestConfigurationProducer.java
@@ -12,7 +12,6 @@ import com.intellij.execution.configurations.ConfigurationTypeUtil;
import com.intellij.execution.junit.RuntimeConfigurationProducer;
import com.intellij.facet.Facet;
import com.intellij.facet.FacetManager;
-import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.vfs.VirtualFile;
@@ -41,16 +40,19 @@ public class PythonUnitTestConfigurationProducer extends RuntimeConfigurationPro
@Override
protected RunnerAndConfigurationSettings createConfigurationByElement(Location location, ConfigurationContext context) {
+ PsiElement element = location.getPsiElement();
+ if (! (TestRunnerService.getInstance(element.getProject()).getProjectConfiguration().equals(
+ PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME))) return null;
RunnerAndConfigurationSettings settings;
+ /*Module module = location.getModule();
- Module module = location.getModule();
if (module != null) {
for (RunnableUnitTestFilter f : Extensions.getExtensions(RunnableUnitTestFilter.EP_NAME)) {
if (f.isRunnableUnitTest(location.getPsiElement().getContainingFile(), module)) {
return null;
}
}
- }
+ }*/
if (PythonUnitTestRunnableScriptFilter.isIfNameMain(location)) {
return null;
@@ -214,7 +216,6 @@ public class PythonUnitTestConfigurationProducer extends RuntimeConfigurationPro
return null;
}
-
public int compareTo(Object o) {
return PREFERED;
}
diff --git a/python/src/com/jetbrains/python/testing/PythonUnitTestConfigurationType.java b/python/src/com/jetbrains/python/testing/PythonUnitTestConfigurationType.java
index 52c546289eca..b817dc0885d8 100644
--- a/python/src/com/jetbrains/python/testing/PythonUnitTestConfigurationType.java
+++ b/python/src/com/jetbrains/python/testing/PythonUnitTestConfigurationType.java
@@ -11,6 +11,7 @@ import javax.swing.*;
* @author Leonid Shalupov
*/
public class PythonUnitTestConfigurationType extends ConfigurationTypeBase {
+
public static PythonUnitTestConfigurationType getInstance() {
return ConfigurationTypeUtil.findConfigurationType(PythonUnitTestConfigurationType.class);
}
diff --git a/python/src/com/jetbrains/python/testing/PythonUnitTestRunConfiguration.java b/python/src/com/jetbrains/python/testing/PythonUnitTestRunConfiguration.java
index 7e09da66211f..6dd91d40967a 100644
--- a/python/src/com/jetbrains/python/testing/PythonUnitTestRunConfiguration.java
+++ b/python/src/com/jetbrains/python/testing/PythonUnitTestRunConfiguration.java
@@ -144,7 +144,7 @@ public class PythonUnitTestRunConfiguration extends AbstractPythonRunConfigurati
throw new RuntimeConfigurationError(PyBundle.message("runcfg.unittest.no_class_name"));
}
- if (StringUtil.isEmptyOrSpaces(myMethodName) && myTestType == TestType.TEST_METHOD) {
+ if (StringUtil.isEmptyOrSpaces(myMethodName) && (myTestType == TestType.TEST_METHOD || myTestType == TestType.TEST_FUNCTION)) {
throw new RuntimeConfigurationError(PyBundle.message("runcfg.unittest.no_method_name"));
}
}
diff --git a/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.form b/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.form
new file mode 100644
index 000000000000..af8eb74a9225
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.form
@@ -0,0 +1,32 @@
+
+
diff --git a/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.java b/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.java
new file mode 100644
index 000000000000..4887637350e4
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.java
@@ -0,0 +1,78 @@
+package com.jetbrains.python.testing;
+
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.NonDefaultProjectConfigurable;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.Nls;
+
+import javax.swing.*;
+import java.util.List;
+
+/**
+ * User: catherine
+ */
+public class TestRunnerConfigurable implements Configurable, NonDefaultProjectConfigurable {
+ private JPanel myMainPanel;
+ private JComboBox myComboBox;
+ private PythonTestConfigurationsModel myModel;
+ private Project myProject;
+
+ public TestRunnerConfigurable(Project project) {
+ myProject = project;
+ }
+ @Nls
+ @Override
+ public String getDisplayName() {
+ return "Test Runner";
+ }
+
+ @Override
+ public Icon getIcon() {
+ return null;
+ }
+
+ @Override
+ public String getHelpTopic() {
+ return "test_runner_configuration";
+ }
+
+ @Override
+ public JComponent createComponent() {
+ List configurations = TestRunnerService.getInstance(myProject).getConfigurations();
+ myModel = new PythonTestConfigurationsModel(configurations, TestRunnerService.getInstance(myProject).getProjectConfiguration(),
+ myProject);
+ updateConfigurations();
+ return myMainPanel;
+ }
+
+ private void updateConfigurations() {
+ myComboBox.setModel(myModel);
+
+ }
+
+ @Override
+ public boolean isModified() {
+ if (myComboBox.getSelectedItem() != myModel.getProjectConfiguration()) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void apply() throws ConfigurationException {
+ myModel.apply();
+ }
+
+ @Override
+ public void reset() {
+ myComboBox.setSelectedItem(myModel.getProjectConfiguration());
+ myComboBox.repaint();
+ myModel.reset();
+ }
+
+ @Override
+ public void disposeUIResources() {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/TestRunnerService.java b/python/src/com/jetbrains/python/testing/TestRunnerService.java
new file mode 100644
index 000000000000..aaca9cc3d93d
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/TestRunnerService.java
@@ -0,0 +1,55 @@
+package com.jetbrains.python.testing;
+
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * User: catherine
+ */
+@State(name = "TestRunnerService",
+ storages = {
+ @Storage(id = "default", file = "$PROJECT_FILE$"),
+ @Storage(id = "dir", file = "$PROJECT_CONFIG_DIR$/testrunner.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+)
+public class TestRunnerService implements PersistentStateComponent {
+ private List myConfigurations = new ArrayList();
+ public String PROJECT_CONFIGURATION = PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME;
+
+ public TestRunnerService() {
+ myConfigurations.add(PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME);
+ myConfigurations.add(PythonTestConfigurationsModel.PYTHONS_NOSETEST_NAME);
+ myConfigurations.add(PythonTestConfigurationsModel.PY_TEST_NAME);
+ }
+
+ public List getConfigurations() {
+ return myConfigurations;
+ }
+ public void registerConfiguration(final String newConfiguration) {
+ myConfigurations.add(newConfiguration);
+ }
+ @Override
+ public TestRunnerService getState() {
+ return this;
+ }
+
+ @Override
+ public void loadState(TestRunnerService state) {
+ XmlSerializerUtil.copyBean(state, this);
+ }
+
+ public void setProjectConfiguration(String projectConfiguration) {
+ PROJECT_CONFIGURATION = projectConfiguration;
+ }
+
+ public static TestRunnerService getInstance(Project project) {
+ return ServiceManager.getService(project, TestRunnerService.class);
+ }
+ public String getProjectConfiguration() {
+ return PROJECT_CONFIGURATION;
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java b/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java
index 6b47f626b7fb..83c6d960257e 100644
--- a/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java
+++ b/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java
@@ -58,7 +58,7 @@ public class PythonDocTestCommandLineState extends PythonTestCommandLineStateBas
break;
case TEST_FOLDER:
if (!myConfig.getPattern().isEmpty())
- specs.add(myConfig.getFolderName() + "/" + ";" + myConfig.getPattern() + "$");
+ specs.add(myConfig.getFolderName() + "/" + ";" + myConfig.getPattern());
else
specs.add(myConfig.getFolderName() + "/");
// TODO[kate]:think about delimiter between folderName and Pattern
diff --git a/python/src/com/jetbrains/python/testing/doctest/PythonDocTestRunConfiguration.java b/python/src/com/jetbrains/python/testing/doctest/PythonDocTestRunConfiguration.java
index 9b3a3a11e632..38451becb5e2 100644
--- a/python/src/com/jetbrains/python/testing/doctest/PythonDocTestRunConfiguration.java
+++ b/python/src/com/jetbrains/python/testing/doctest/PythonDocTestRunConfiguration.java
@@ -144,7 +144,7 @@ public class PythonDocTestRunConfiguration extends AbstractPythonRunConfiguratio
throw new RuntimeConfigurationError(PyBundle.message("runcfg.unittest.no_class_name"));
}
- if (StringUtil.isEmptyOrSpaces(myMethodName) && myTestType == TestType.TEST_METHOD) {
+ if (StringUtil.isEmptyOrSpaces(myMethodName) && (myTestType == TestType.TEST_METHOD || myTestType == TestType.TEST_FUNCTION)) {
throw new RuntimeConfigurationError(PyBundle.message("runcfg.unittest.no_method_name"));
}
}
diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestCommandLineState.java b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestCommandLineState.java
new file mode 100644
index 000000000000..657779b262f8
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestCommandLineState.java
@@ -0,0 +1,58 @@
+package com.jetbrains.python.testing.nosetest;
+
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.execution.configurations.ParamsGroup;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.jetbrains.python.PythonHelpersLocator;
+import com.jetbrains.python.testing.PythonTestCommandLineStateBase;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * User: catherine
+ */
+public class PythonNoseTestCommandLineState extends PythonTestCommandLineStateBase {
+ private final PythonNoseTestRunConfiguration myConfig;
+ private static final String NOSERUNNER_PY = "pycharm/noserunner.py";
+
+ public PythonNoseTestCommandLineState(PythonNoseTestRunConfiguration runConfiguration, ExecutionEnvironment env) {
+ super(runConfiguration, env);
+ myConfig = runConfiguration;
+ }
+
+ protected void addTestRunnerParameters(GeneralCommandLine cmd) {
+ ParamsGroup script_params = cmd.getParametersList().getParamsGroup(GROUP_SCRIPT);
+ assert script_params != null;
+ script_params.addParameter(new File(PythonHelpersLocator.getHelpersRoot(), NOSERUNNER_PY).getAbsolutePath());
+ script_params.addParameters(getTestSpecs());
+ }
+
+ private List getTestSpecs() {
+ List specs = new ArrayList();
+
+ switch (myConfig.getTestType()) {
+ case TEST_SCRIPT:
+ specs.add(myConfig.getScriptName());
+ break;
+ case TEST_CLASS:
+ specs.add(myConfig.getScriptName() + "::" + myConfig.getClassName());
+ break;
+ case TEST_METHOD:
+ specs.add(myConfig.getScriptName() + "::" + myConfig.getClassName() + "::" + myConfig.getMethodName());
+ break;
+ case TEST_FOLDER:
+ specs.add(myConfig.getFolderName() + "/");
+ break;
+ case TEST_FUNCTION:
+ specs.add(myConfig.getScriptName() + "::::" + myConfig.getMethodName());
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown test type: " + myConfig.getTestType());
+ }
+ if (!myConfig.getParams().isEmpty())
+ specs.add(myConfig.getParams());
+ return specs;
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestConfigurationProducer.java b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestConfigurationProducer.java
new file mode 100644
index 000000000000..f7e70a5c958e
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestConfigurationProducer.java
@@ -0,0 +1,223 @@
+/*
+ * User: anna
+ * Date: 13-May-2010
+ */
+package com.jetbrains.python.testing.nosetest;
+
+import com.intellij.execution.Location;
+import com.intellij.execution.RunManager;
+import com.intellij.execution.RunnerAndConfigurationSettings;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.configurations.ConfigurationTypeUtil;
+import com.intellij.execution.junit.RuntimeConfigurationProducer;
+import com.intellij.facet.Facet;
+import com.intellij.facet.FacetManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.jetbrains.python.PythonModuleTypeBase;
+import com.jetbrains.python.facet.PythonFacetSettings;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.testing.PythonTestConfigurationsModel;
+import com.jetbrains.python.testing.PythonUnitTestRunnableScriptFilter;
+import com.jetbrains.python.testing.PythonUnitTestUtil;
+import com.jetbrains.python.testing.TestRunnerService;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+public class PythonNoseTestConfigurationProducer extends RuntimeConfigurationProducer {
+ private PsiElement myPsiElement;
+
+ public PythonNoseTestConfigurationProducer() {
+ super(ConfigurationTypeUtil.findConfigurationType(PythonNoseTestRunConfigurationType.class));
+ }
+
+ @Override
+ public PsiElement getSourceElement() {
+ return myPsiElement;
+ }
+
+ @Override
+ protected RunnerAndConfigurationSettings createConfigurationByElement(Location location, ConfigurationContext context) {
+ PsiElement element = location.getPsiElement();
+ if (! (TestRunnerService.getInstance(element.getProject()).getProjectConfiguration().equals(
+ PythonTestConfigurationsModel.PYTHONS_NOSETEST_NAME))) return null;
+ RunnerAndConfigurationSettings settings;
+ /*Module module = location.getModule();
+
+ if (module != null) {
+ for (RunnableUnitTestFilter f : Extensions.getExtensions(RunnableUnitTestFilter.EP_NAME)) {
+ if (f.isRunnableUnitTest(location.getPsiElement().getContainingFile(), module)) {
+ return null;
+ }
+ }
+ }*/
+ if (PythonUnitTestRunnableScriptFilter.isIfNameMain(location)) {
+ return null;
+ }
+ settings = createConfigurationFromFolder(location);
+ if (settings != null) return settings;
+ final PyElement pyElement = PsiTreeUtil.getParentOfType(location.getPsiElement(), PyElement.class);
+ if (pyElement != null) {
+ settings = createConfigurationFromFunction(location, pyElement);
+ if (settings != null) return settings;
+ settings = createConfigurationFromClass(location, pyElement);
+ if (settings != null) return settings;
+ }
+
+ settings = createConfigurationFromFile(location, location.getPsiElement());
+ if (settings != null) return settings;
+ return null;
+ }
+
+ @Nullable
+ private RunnerAndConfigurationSettings createConfigurationFromFunction(Location location, PyElement element) {
+ PyFunction pyFunction = PsiTreeUtil.getParentOfType(element, PyFunction.class, false);
+ if (pyFunction == null || !PythonUnitTestUtil.isTestCaseFunction(pyFunction)) return null;
+ final PyClass containingClass = pyFunction.getContainingClass();
+ final RunnerAndConfigurationSettings settings = makeConfigurationSettings(location, "tests from function");
+ final PythonNoseTestRunConfiguration configuration = (PythonNoseTestRunConfiguration)settings.getConfiguration();
+ configuration.setMethodName(pyFunction.getName());
+ if (containingClass != null) {
+ configuration.setClassName(containingClass.getName());
+ configuration.setTestType(PythonNoseTestRunConfiguration.TestType.TEST_METHOD);
+ }
+ else {
+ configuration.setTestType(PythonNoseTestRunConfiguration.TestType.TEST_FUNCTION);
+ }
+ if (!setupConfigurationScript(configuration, pyFunction)) return null;
+ configuration.setName(configuration.suggestedName());
+ myPsiElement = pyFunction;
+ return settings;
+ }
+
+ @Nullable
+ private RunnerAndConfigurationSettings createConfigurationFromClass(Location location, PyElement element) {
+ PyClass pyClass = PsiTreeUtil.getParentOfType(element, PyClass.class, false);
+ if (pyClass == null || !PythonUnitTestUtil.isTestCaseClass(pyClass)) return null;
+
+ final RunnerAndConfigurationSettings settings = makeConfigurationSettings(location, "tests from class");
+ final PythonNoseTestRunConfiguration configuration = (PythonNoseTestRunConfiguration)settings.getConfiguration();
+
+ configuration.setTestType(
+ PythonNoseTestRunConfiguration.TestType.TEST_CLASS);
+ configuration.setClassName(pyClass.getName());
+ if (!setupConfigurationScript(configuration, pyClass)) return null;
+ configuration.setName(configuration.suggestedName());
+
+ myPsiElement = pyClass;
+ return settings;
+ }
+
+ @Nullable
+ private RunnerAndConfigurationSettings createConfigurationFromFolder(Location location) {
+ final PsiElement element = location.getPsiElement();
+
+ if (!(element instanceof PsiDirectory)) return null;
+
+ final Module module = location.getModule();
+ if (!isPythonModule(module)) return null;
+
+ PsiDirectory dir = (PsiDirectory)element;
+ final VirtualFile file = dir.getVirtualFile();
+ final String path = file.getPath();
+
+ final RunnerAndConfigurationSettings settings = makeConfigurationSettings(location, "tests from class");
+ final PythonNoseTestRunConfiguration configuration = (PythonNoseTestRunConfiguration)settings.getConfiguration();
+
+ configuration.setTestType(PythonNoseTestRunConfiguration.TestType.TEST_FOLDER);
+ configuration.setFolderName(path);
+ configuration.setWorkingDirectory(path);
+
+ configuration.setName(configuration.suggestedName());
+ myPsiElement = dir;
+ return settings;
+ }
+
+
+ private static boolean isPythonModule(Module module) {
+ if (module == null) {
+ return false;
+ }
+ if (module.getModuleType() instanceof PythonModuleTypeBase) {
+ return true;
+ }
+ final Facet[] allFacets = FacetManager.getInstance(module).getAllFacets();
+ for (Facet facet : allFacets) {
+ if (facet.getConfiguration() instanceof PythonFacetSettings) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ private RunnerAndConfigurationSettings createConfigurationFromFile(Location location, PsiElement element) {
+ PsiElement file = element.getContainingFile();
+ if (file == null || !(file instanceof PyFile)) return null;
+
+ final PyFile pyFile = (PyFile)file;
+ final List testCases = PythonUnitTestUtil.getTestCaseClassesFromFile(pyFile);
+ if (testCases.isEmpty()) return null;
+
+ final RunnerAndConfigurationSettings settings = makeConfigurationSettings(location, "tests from file");
+ final PythonNoseTestRunConfiguration configuration = (PythonNoseTestRunConfiguration)settings.getConfiguration();
+
+ configuration.setTestType(PythonNoseTestRunConfiguration.TestType.TEST_SCRIPT);
+ if (!setupConfigurationScript(configuration, pyFile)) return null;
+
+ configuration.setName(configuration.suggestedName());
+ myPsiElement = pyFile;
+ return settings;
+ }
+
+ private RunnerAndConfigurationSettings makeConfigurationSettings(Location location, String name) {
+ final RunnerAndConfigurationSettings result =
+ RunManager.getInstance(location.getProject()).createRunConfiguration(name, getConfigurationFactory());
+ PythonNoseTestRunConfiguration configuration = (PythonNoseTestRunConfiguration)result.getConfiguration();
+ configuration.setUseModuleSdk(true);
+ configuration.setModule(ModuleUtil.findModuleForPsiElement(location.getPsiElement()));
+ return result;
+ }
+
+ private static boolean setupConfigurationScript(PythonNoseTestRunConfiguration cfg, PyElement element) {
+ final PyFile containingFile = PyUtil.getContainingPyFile(element);
+ if (containingFile == null) return false;
+ final VirtualFile vFile = containingFile.getVirtualFile();
+ if (vFile == null) return false;
+ final VirtualFile parent = vFile.getParent();
+ if (parent == null) return false;
+
+ cfg.setScriptName(containingFile.getName());
+ cfg.setWorkingDirectory(parent.getPath());
+
+ return true;
+ }
+
+ @Override
+ protected RunnerAndConfigurationSettings findExistingByElement(Location location,
+ @NotNull RunnerAndConfigurationSettings[] existingConfigurations,
+ ConfigurationContext context) {
+ final RunnerAndConfigurationSettings settings = createConfigurationByElement(location, null);
+ if (settings != null) {
+ final PythonNoseTestRunConfiguration configuration = (PythonNoseTestRunConfiguration)settings.getConfiguration();
+ for (RunnerAndConfigurationSettings existingConfiguration : existingConfigurations) {
+ if (configuration.compareSettings((PythonNoseTestRunConfiguration)existingConfiguration.getConfiguration())) {
+ return existingConfiguration;
+ }
+ }
+ }
+ return null;
+ }
+
+
+ public int compareTo(Object o) {
+ return PREFERED;
+ }
+
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfiguration.java b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfiguration.java
new file mode 100644
index 000000000000..2639f9981daa
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfiguration.java
@@ -0,0 +1,216 @@
+package com.jetbrains.python.testing.nosetest;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.*;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.options.SettingsEditor;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.JDOMExternalizerUtil;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.run.AbstractPythonRunConfiguration;
+import com.jetbrains.python.run.AbstractPythonRunConfigurationParams;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * User: catherine
+ */
+public class PythonNoseTestRunConfiguration extends AbstractPythonRunConfiguration
+ implements AbstractPythonRunConfigurationParams,
+ PythonNoseTestRunConfigurationParams {
+ private String myClassName = "";
+ private String myScriptName = "";
+ private String myMethodName = "";
+ private String myFolderName = "";
+ private TestType myTestType = TestType.TEST_SCRIPT;
+ private String myParams = ""; // parameters for nosetests
+
+ protected PythonNoseTestRunConfiguration(RunConfigurationModule module,
+ ConfigurationFactory configurationFactory, String name) {
+ super(name, module, configurationFactory);
+ }
+
+ @Override
+ protected ModuleBasedConfiguration createInstance() {
+ return new PythonNoseTestRunConfiguration(getConfigurationModule(), getFactory(), getName());
+ }
+
+ public enum TestType {
+ TEST_FOLDER,
+ TEST_SCRIPT,
+ TEST_CLASS,
+ TEST_METHOD,
+ TEST_FUNCTION,}
+
+ @Override
+ public void readExternal(Element element) throws InvalidDataException {
+ super.readExternal(element);
+ myParams = JDOMExternalizerUtil.readField(element, "PARAMS");
+ myScriptName = JDOMExternalizerUtil.readField(element, "SCRIPT_NAME");
+ myClassName = JDOMExternalizerUtil.readField(element, "CLASS_NAME");
+ myMethodName = JDOMExternalizerUtil.readField(element, "METHOD_NAME");
+ myFolderName = JDOMExternalizerUtil.readField(element, "FOLDER_NAME");
+
+ try {
+ myTestType = TestType.valueOf(JDOMExternalizerUtil.readField(element, "TEST_TYPE"));
+ }
+ catch (IllegalArgumentException e) {
+ myTestType = TestType.TEST_SCRIPT; // safe default
+ }
+ }
+
+ @Override
+ public void writeExternal(Element element) throws WriteExternalException {
+ super.writeExternal(element);
+ JDOMExternalizerUtil.writeField(element, "SCRIPT_NAME", myScriptName);
+ JDOMExternalizerUtil.writeField(element, "CLASS_NAME", myClassName);
+ JDOMExternalizerUtil.writeField(element, "METHOD_NAME", myMethodName);
+ JDOMExternalizerUtil.writeField(element, "FOLDER_NAME", myFolderName);
+ JDOMExternalizerUtil.writeField(element, "TEST_TYPE", myTestType.toString());
+ JDOMExternalizerUtil.writeField(element, "PARAMS", myParams);
+ }
+
+ @Override
+ public SettingsEditor extends RunConfiguration> getConfigurationEditor() {
+ return new PythonNoseTestRunConfigurationEditor(getProject(), this);
+ }
+
+ public String getParams() {
+ return myParams;
+ }
+
+ public void setParams(String pattern) {
+ myParams = pattern;
+ }
+
+ public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException {
+ return new PythonNoseTestCommandLineState(this, env);
+ }
+
+ @Override
+ public AbstractPythonRunConfigurationParams getBaseParams() {
+ return this;
+ }
+
+ public String getClassName() {
+ return myClassName;
+ }
+
+ public void setClassName(String className) {
+ myClassName = className;
+ }
+
+ public String getFolderName() {
+ return myFolderName;
+ }
+
+ public void setFolderName(String folderName) {
+ myFolderName = folderName;
+ }
+
+ public String getScriptName() {
+ return myScriptName;
+ }
+
+ public void setScriptName(String scriptName) {
+ myScriptName = scriptName;
+ }
+
+ public String getMethodName() {
+ return myMethodName;
+ }
+
+ public void setMethodName(String methodName) {
+ myMethodName = methodName;
+ }
+
+ public TestType getTestType() {
+ return myTestType;
+ }
+
+ public void setTestType(TestType testType) {
+ myTestType = testType;
+ }
+ @Override
+ public String suggestedName() {
+ switch (myTestType) {
+ case TEST_CLASS:
+ return "Nosetests in " + myClassName;
+ case TEST_METHOD:
+ return "Nosetests " + myClassName + "." + myMethodName;
+ case TEST_SCRIPT:
+ return "Nosetests in " + myScriptName;
+ case TEST_FOLDER:
+ return "Nosetests in " + FileUtil.toSystemDependentName(myFolderName);
+ case TEST_FUNCTION:
+ return "Nosetests " + myMethodName;
+ default:
+ throw new IllegalStateException("Unknown test type: " + myTestType);
+ }
+ }
+
+ public static void copyParams(PythonNoseTestRunConfigurationParams source, PythonNoseTestRunConfigurationParams target) {
+ AbstractPythonRunConfiguration.copyParams(source.getBaseParams(), target.getBaseParams());
+ target.setScriptName(source.getScriptName());
+ target.setClassName(source.getClassName());
+ target.setFolderName(source.getFolderName());
+ target.setMethodName(source.getMethodName());
+ target.setTestType(source.getTestType());
+ target.setParams(source.getParams());
+ }
+
+ public boolean compareSettings(PythonNoseTestRunConfiguration cfg) {
+ if (cfg == null) return false;
+
+ if (getTestType() != cfg.getTestType()) return false;
+
+ switch (getTestType()) {
+ case TEST_FOLDER:
+ return getFolderName().equals(cfg.getFolderName());
+ case TEST_SCRIPT:
+ return getScriptName().equals(cfg.getScriptName()) &&
+ getWorkingDirectory().equals(cfg.getWorkingDirectory());
+ case TEST_CLASS:
+ return getScriptName().equals(cfg.getScriptName()) &&
+ getWorkingDirectory().equals(cfg.getWorkingDirectory()) &&
+ getClassName().equals(cfg.getClassName());
+ case TEST_METHOD:
+ return getScriptName().equals(cfg.getScriptName()) &&
+ getWorkingDirectory().equals(cfg.getWorkingDirectory()) &&
+ getClassName().equals(cfg.getClassName()) &&
+ getMethodName().equals(cfg.getMethodName());
+ case TEST_FUNCTION:
+ return getScriptName().equals(cfg.getScriptName()) &&
+ getWorkingDirectory().equals(cfg.getWorkingDirectory()) &&
+ getMethodName().equals(cfg.getMethodName());
+ default:
+ throw new IllegalStateException("Unknown test type: " + getTestType());
+ }
+ }
+ @Override
+ public void checkConfiguration() throws RuntimeConfigurationException {
+ super.checkConfiguration();
+
+ if (StringUtil.isEmptyOrSpaces(myFolderName) && myTestType == TestType.TEST_FOLDER) {
+ throw new RuntimeConfigurationError(
+ PyBundle.message("runcfg.unittest.no_folder_name"));
+ }
+
+ if (StringUtil.isEmptyOrSpaces(getScriptName()) && myTestType != TestType.TEST_FOLDER) {
+ throw new RuntimeConfigurationError(PyBundle.message("runcfg.unittest.no_script_name"));
+ }
+
+ if (StringUtil.isEmptyOrSpaces(myClassName) && (myTestType == TestType.TEST_METHOD || myTestType == TestType.TEST_CLASS)) {
+ throw new RuntimeConfigurationError(PyBundle.message("runcfg.unittest.no_class_name"));
+ }
+
+ if (StringUtil.isEmptyOrSpaces(myMethodName) && (myTestType == TestType.TEST_METHOD || myTestType == TestType.TEST_FUNCTION)) {
+ throw new RuntimeConfigurationError(PyBundle.message("runcfg.unittest.no_method_name"));
+ }
+ }
+
+}
diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationEditor.java b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationEditor.java
new file mode 100644
index 000000000000..ee407fcafead
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationEditor.java
@@ -0,0 +1,36 @@
+package com.jetbrains.python.testing.nosetest;
+
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SettingsEditor;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+ * User: catherine
+ */
+public class PythonNoseTestRunConfigurationEditor extends SettingsEditor {
+ private PythonNoseTestRunConfigurationForm myForm;
+
+ public PythonNoseTestRunConfigurationEditor(final Project project, final PythonNoseTestRunConfiguration configuration) {
+ myForm = new PythonNoseTestRunConfigurationForm(project, configuration);
+ }
+
+ protected void resetEditorFrom(final PythonNoseTestRunConfiguration config) {
+ PythonNoseTestRunConfiguration.copyParams(config, myForm);
+ }
+
+ protected void applyEditorTo(final PythonNoseTestRunConfiguration config) throws ConfigurationException {
+ PythonNoseTestRunConfiguration.copyParams(myForm, config);
+ }
+
+ @NotNull
+ protected JComponent createEditor() {
+ return myForm.getPanel();
+ }
+
+ protected void disposeEditor() {
+ myForm = null;
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationForm.form b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationForm.form
new file mode 100644
index 000000000000..825072f9e554
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationForm.form
@@ -0,0 +1,138 @@
+
+
diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationForm.java b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationForm.java
new file mode 100644
index 000000000000..60c81997b025
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationForm.java
@@ -0,0 +1,229 @@
+package com.jetbrains.python.testing.nosetest;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.LabeledComponent;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.io.FileUtil;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.run.AbstractPyCommonOptionsForm;
+import com.jetbrains.python.run.AbstractPythonRunConfigurationParams;
+import com.jetbrains.python.run.PyCommonOptionsFormFactory;
+import com.jetbrains.python.run.PythonRunConfigurationFormUtil;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import static com.intellij.openapi.util.io.FileUtil.toSystemIndependentName;
+
+/**
+ * User: catherine
+ */
+public class PythonNoseTestRunConfigurationForm implements PythonNoseTestRunConfigurationParams {
+private JPanel myRootPanel;
+ private LabeledComponent myTestClassComponent;
+ private LabeledComponent myTestMethodComponent;
+ private LabeledComponent myTestFolderComponent;
+ private LabeledComponent myTestScriptComponent;
+ private JRadioButton myAllInFolderRB;
+ private JRadioButton myTestScriptRB;
+ private JRadioButton myTestClassRB;
+ private JRadioButton myTestMethodRB;
+ private JPanel myCommonOptionsPlaceholder;
+ private LabeledComponent myParamComponent;
+ private JRadioButton myTestFunctionRB;
+
+ private TextFieldWithBrowseButton myTestFolderTextField;
+ private TextFieldWithBrowseButton myTestScriptTextField;
+ private JTextField myTestMethodTextField;
+ private JTextField myTestClassTextField;
+ private JTextField myParamTextField;
+
+ private final Project myProject;
+ private final AbstractPyCommonOptionsForm myCommonOptionsForm;
+
+ public PythonNoseTestRunConfigurationForm(final Project project, final PythonNoseTestRunConfiguration configuration) {
+ myProject = project;
+
+ myCommonOptionsForm = PyCommonOptionsFormFactory.getInstance().createForm(configuration);
+ myCommonOptionsPlaceholder.add(myCommonOptionsForm.getMainPanel(), BorderLayout.CENTER);
+
+ initComponents();
+ }
+
+ public AbstractPythonRunConfigurationParams getBaseParams() {
+ return myCommonOptionsForm;
+ }
+
+ private void initComponents() {
+
+ final ActionListener testTypeListener = new ActionListener() {
+ public void actionPerformed(final ActionEvent e) {
+ setTestType(getTestType());
+ }
+ };
+
+ myAllInFolderRB.addActionListener(testTypeListener);
+ myTestScriptRB.addActionListener(testTypeListener);
+ myTestClassRB.addActionListener(testTypeListener);
+ myTestMethodRB.addActionListener(testTypeListener);
+ myTestFunctionRB.addActionListener(testTypeListener);
+ }
+
+ public String getClassName() {
+ return myTestClassTextField.getText().trim();
+ }
+
+ public void setClassName(String className) {
+ myTestClassTextField.setText(className);
+ }
+
+ public String getParams() {
+ return myParamTextField.getText().trim();
+ }
+
+ public void setParams(String params) {
+ myParamTextField.setText(params);
+ }
+
+ public String getFolderName() {
+ return toSystemIndependentName(myTestFolderTextField.getText().trim());
+ }
+
+ public void setFolderName(String folderName) {
+ myTestFolderTextField.setText(FileUtil.toSystemDependentName(folderName));
+ }
+
+ public String getScriptName() {
+ return toSystemIndependentName(myTestScriptTextField.getText().trim());
+ }
+
+ public void setScriptName(String scriptName) {
+ myTestScriptTextField.setText(FileUtil.toSystemDependentName(scriptName));
+ }
+
+ public String getMethodName() {
+ return myTestMethodTextField.getText().trim();
+ }
+
+ public void setMethodName(String methodName) {
+ myTestMethodTextField.setText(methodName);
+ }
+
+ public PythonNoseTestRunConfiguration.TestType getTestType() {
+ if (myAllInFolderRB.isSelected()) {
+ return PythonNoseTestRunConfiguration.TestType.TEST_FOLDER;
+ }
+ else if (myTestScriptRB.isSelected()) {
+ return PythonNoseTestRunConfiguration.TestType.TEST_SCRIPT;
+ }
+ else if (myTestClassRB.isSelected()) {
+ return PythonNoseTestRunConfiguration.TestType.TEST_CLASS;
+ }
+ else if (myTestMethodRB.isSelected()) {
+ return PythonNoseTestRunConfiguration.TestType.TEST_METHOD;
+ }
+ else {
+ return PythonNoseTestRunConfiguration.TestType.TEST_FUNCTION;
+ }
+ }
+
+ private static void setSelectedIfNeeded(boolean condition, JRadioButton rb) {
+ if (condition) {
+ rb.setSelected(true);
+ }
+ }
+
+ public void setTestType(PythonNoseTestRunConfiguration.TestType testType) {
+ setSelectedIfNeeded(testType == PythonNoseTestRunConfiguration.TestType.TEST_FOLDER, myAllInFolderRB);
+ setSelectedIfNeeded(testType == PythonNoseTestRunConfiguration.TestType.TEST_SCRIPT, myTestScriptRB);
+ setSelectedIfNeeded(testType == PythonNoseTestRunConfiguration.TestType.TEST_CLASS, myTestClassRB);
+ setSelectedIfNeeded(testType == PythonNoseTestRunConfiguration.TestType.TEST_METHOD, myTestMethodRB);
+ setSelectedIfNeeded(testType == PythonNoseTestRunConfiguration.TestType.TEST_FUNCTION, myTestFunctionRB);
+
+ myTestFolderComponent.setEnabled(testType == PythonNoseTestRunConfiguration.TestType.TEST_FOLDER);
+ myTestScriptComponent.setEnabled(testType != PythonNoseTestRunConfiguration.TestType.TEST_FOLDER);
+ myTestClassComponent.setEnabled(testType == PythonNoseTestRunConfiguration.TestType.TEST_CLASS || testType == PythonNoseTestRunConfiguration.TestType.TEST_METHOD);
+ myTestMethodComponent.setEnabled(testType == PythonNoseTestRunConfiguration.TestType.TEST_METHOD || testType == PythonNoseTestRunConfiguration.TestType.TEST_FUNCTION);
+ }
+
+ public JComponent getPanel() {
+ return myRootPanel;
+ }
+
+ private static LabeledComponent createTestFolderComponent(final Ref testsFolderTextFieldWrapper) {
+ final TextFieldWithBrowseButton testsFolderTextField = new TextFieldWithBrowseButton();
+ testsFolderTextFieldWrapper.set(testsFolderTextField);
+
+ LabeledComponent myComponent = new LabeledComponent();
+ myComponent.setComponent(testsFolderTextField);
+ myComponent.setText(PyBundle.message("runcfg.unittest.dlg.folder_path"));
+
+ return myComponent;
+ }
+
+ public static LabeledComponent createScriptPathComponent(final Ref testScriptTextFieldWrapper,
+ final String text) {
+ final TextFieldWithBrowseButton testScriptTextField = new TextFieldWithBrowseButton();
+ testScriptTextFieldWrapper.set(testScriptTextField);
+
+ LabeledComponent myComponent = new LabeledComponent();
+ myComponent.setComponent(testScriptTextField);
+ myComponent.setText(text);
+
+ return myComponent;
+ }
+
+ private LabeledComponent createTestClassComponent() {
+ myTestClassTextField = new JTextField();
+
+ LabeledComponent myComponent = new LabeledComponent();
+ myComponent.setComponent(myTestClassTextField);
+ myComponent.setText(PyBundle.message("runcfg.unittest.dlg.class_label"));
+
+ return myComponent;
+ }
+
+ private LabeledComponent createTestMethodComponent() {
+ myTestMethodTextField = new JTextField();
+
+ LabeledComponent myComponent = new LabeledComponent();
+
+ myComponent.setComponent(myTestMethodTextField);
+ myComponent.setText(PyBundle.message("runcfg.unittest.dlg.method_label"));
+
+ return myComponent;
+ }
+
+ private void createUIComponents() {
+ myTestClassComponent = createTestClassComponent();
+ myTestMethodComponent = createTestMethodComponent();
+ myParamComponent = createParamComponent();
+
+ final Ref testsFolderTextFieldWrapper = new Ref();
+ myTestFolderComponent = createTestFolderComponent(testsFolderTextFieldWrapper);
+ myTestFolderTextField = testsFolderTextFieldWrapper.get();
+ String title = PyBundle.message("runcfg.unittest.dlg.select.folder.path");
+ PythonRunConfigurationFormUtil.addFolderChooser(title, myTestFolderTextField, myProject);
+
+ final Ref testScriptTextFieldWrapper = new Ref();
+ myTestScriptComponent = createScriptPathComponent(testScriptTextFieldWrapper, PyBundle.message("runcfg.unittest.dlg.folder_path"));
+ myTestScriptTextField = testScriptTextFieldWrapper.get();
+ title = PyBundle.message("runcfg.unittest.dlg.select.script.path");
+ PythonRunConfigurationFormUtil.addFileChooser(title, myTestScriptTextField, myProject);
+ }
+
+ private LabeledComponent createParamComponent() {
+ myParamTextField = new JTextField();
+
+ LabeledComponent myComponent = new LabeledComponent();
+ myComponent.setComponent(myParamTextField);
+ myComponent.setText("Param");
+
+ return myComponent;
+ }
+}
+
+
diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationParams.java b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationParams.java
new file mode 100644
index 000000000000..bafd9b7f0430
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationParams.java
@@ -0,0 +1,28 @@
+package com.jetbrains.python.testing.nosetest;
+
+import com.jetbrains.python.run.AbstractPythonRunConfigurationParams;
+
+/**
+ * User: catherine
+ */
+public interface PythonNoseTestRunConfigurationParams {
+ AbstractPythonRunConfigurationParams getBaseParams();
+
+ String getClassName();
+ void setClassName(String className);
+
+ String getFolderName();
+ void setFolderName(String folderName);
+
+ String getScriptName();
+ void setScriptName(String scriptName);
+
+ String getMethodName();
+ void setMethodName(String methodName);
+
+ PythonNoseTestRunConfiguration.TestType getTestType();
+ void setTestType(PythonNoseTestRunConfiguration.TestType testType);
+
+ String getParams();
+ void setParams(String params);
+}
diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationType.java b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationType.java
new file mode 100644
index 000000000000..20d2b3b584f5
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfigurationType.java
@@ -0,0 +1,37 @@
+package com.jetbrains.python.testing.nosetest;
+
+import com.intellij.execution.configurations.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.IconLoader;
+import com.jetbrains.python.PyBundle;
+
+import javax.swing.*;
+
+/**
+ * User: catherine
+ */
+public class PythonNoseTestRunConfigurationType extends ConfigurationTypeBase {
+ public static PythonNoseTestRunConfigurationType getInstance() {
+ return ConfigurationTypeUtil.findConfigurationType(PythonNoseTestRunConfigurationType.class);
+ }
+
+ public PythonNoseTestRunConfigurationType() {
+ super("PythonNoseTestRunConfigurationType",
+ PyBundle.message("runcfg.nosetests.display_name"),
+ PyBundle.message("runcfg.nosetests.description"),
+ ICON);
+ addFactory(new PythonNoseTestConfigurationFactory(this));
+ }
+
+ private final static Icon ICON = IconLoader.getIcon("/com/jetbrains/python/icons/python.png");
+
+ private static class PythonNoseTestConfigurationFactory extends ConfigurationFactory {
+ protected PythonNoseTestConfigurationFactory(ConfigurationType configurationType) {
+ super(configurationType);
+ }
+
+ public RunConfiguration createTemplateConfiguration(Project project) {
+ return new PythonNoseTestRunConfiguration(new RunConfigurationModule(project), this, "");
+ }
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java b/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java
index aee46dff3052..6cfd5876e87a 100644
--- a/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java
+++ b/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java
@@ -20,8 +20,9 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyStatement;
+import com.jetbrains.python.testing.PythonTestConfigurationsModel;
import com.jetbrains.python.testing.PythonUnitTestConfigurationProducer;
-import com.jetbrains.python.testing.PythonUnitTestUtil;
+import com.jetbrains.python.testing.TestRunnerService;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -43,6 +44,9 @@ public class PyTestConfigurationProducer extends RuntimeConfigurationProducer {
@Override
protected RunnerAndConfigurationSettings createConfigurationByElement(Location location, ConfigurationContext context) {
PsiElement element = location.getPsiElement();
+ if (! (TestRunnerService.getInstance(element.getProject()).getProjectConfiguration().equals(
+ PythonTestConfigurationsModel.PY_TEST_NAME))) return null;
+
PsiFileSystemItem file = element instanceof PsiDirectory ? (PsiDirectory)element : element.getContainingFile();
if (file == null) return null;
myPsiElement = file;
@@ -74,6 +78,7 @@ public class PyTestConfigurationProducer extends RuntimeConfigurationProducer {
configuration.setName(name + " in " + configuration.getName());
myPsiElement = pyFunction;
}
+ configuration.setName(configuration.suggestedName());
return result;
}
diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java b/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java
index b98f84c07fbf..f7f8af48618f 100644
--- a/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java
+++ b/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java
@@ -106,4 +106,8 @@ public class PyTestRunConfiguration extends AbstractPythonRunConfiguration {
if (runner.exists()) return runner.getPath();
return null;
}
+ @Override
+ public String suggestedName() {
+ return "py.test in " + getName();
+ }
}
diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfigurationType.java b/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfigurationType.java
index 00e060dbe8eb..e176f7baf130 100644
--- a/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfigurationType.java
+++ b/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfigurationType.java
@@ -5,6 +5,7 @@ import com.intellij.execution.configurations.ConfigurationType;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.configurations.RunConfigurationModule;
import com.intellij.openapi.project.Project;
+import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonFileType;
import org.jetbrains.annotations.NotNull;
@@ -17,11 +18,11 @@ public class PyTestRunConfigurationType implements ConfigurationType {
private final ConfigurationFactory myPyTestConfigurationFactory = new PyTestRunConfigurationFactory(this);
public String getDisplayName() {
- return "py.test";
+ return PyBundle.message("runcfg.pytest.display_name");
}
public String getConfigurationTypeDescription() {
- return "py.test";
+ return PyBundle.message("runcfg.pytest.description");
}
public Icon getIcon() {
diff --git a/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java b/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java
index 464b3ccb6b40..e3c7203ba1e2 100644
--- a/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java
+++ b/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java
@@ -17,8 +17,7 @@ public class AssignTargetAnnotator extends PyAnnotator {
@Override
public void visitPyAssignmentStatement(final PyAssignmentStatement node) {
- final PyExpression expression = node.getLeftHandSideExpression();
- if (expression != null) {
+ for (PyExpression expression : node.getRawTargets()) {
expression.accept(new ExprVisitor(Operation.Assign));
}
}
@@ -53,21 +52,21 @@ public class AssignTargetAnnotator extends PyAnnotator {
}
private class ExprVisitor extends PyElementVisitor {
- private final Operation _op;
+ private final Operation myOp;
private final String DELETING_NONE = message("ANN.deleting.none");
private final String ASSIGNMENT_TO_NONE = message("ANN.assign.to.none");
private final String CANT_ASSIGN_TO_FUNCTION_CALL = message("ANN.cant.assign.to.call");
private final String CANT_DELETE_FUNCTION_CALL = message("ANN.cant.delete.call");
public ExprVisitor(Operation op) {
- _op = op;
+ myOp = op;
}
@Override
public void visitPyReferenceExpression(final PyReferenceExpression node) {
String referencedName = node.getReferencedName();
if (PyNames.NONE.equals(referencedName)) {
- getHolder().createErrorAnnotation(node, (_op == Operation.Delete) ? DELETING_NONE : ASSIGNMENT_TO_NONE);
+ getHolder().createErrorAnnotation(node, (myOp == Operation.Delete) ? DELETING_NONE : ASSIGNMENT_TO_NONE);
}
}
@@ -77,7 +76,7 @@ public class AssignTargetAnnotator extends PyAnnotator {
if (PyNames.NONE.equals(targetName)) {
final VirtualFile vfile = node.getContainingFile().getVirtualFile();
if (vfile != null && !vfile.getUrl().contains("/" + PythonSdkType.SKELETON_DIR_NAME + "/")){
- getHolder().createErrorAnnotation(node, (_op == Operation.Delete) ? DELETING_NONE : ASSIGNMENT_TO_NONE);
+ getHolder().createErrorAnnotation(node, (myOp == Operation.Delete) ? DELETING_NONE : ASSIGNMENT_TO_NONE);
}
}
if (PyNames.DEBUG.equals(targetName)) {
@@ -92,13 +91,13 @@ public class AssignTargetAnnotator extends PyAnnotator {
@Override
public void visitPyCallExpression(final PyCallExpression node) {
- getHolder().createErrorAnnotation(node, (_op == Operation.Delete) ? CANT_DELETE_FUNCTION_CALL : CANT_ASSIGN_TO_FUNCTION_CALL);
+ getHolder().createErrorAnnotation(node, (myOp == Operation.Delete) ? CANT_DELETE_FUNCTION_CALL : CANT_ASSIGN_TO_FUNCTION_CALL);
}
@Override
public void visitPyGeneratorExpression(final PyGeneratorExpression node) {
getHolder().createErrorAnnotation(node, message(
- _op == Operation.AugAssign ? "ANN.cant.aug.assign.to.generator" : "ANN.cant.assign.to.generator"));
+ myOp == Operation.AugAssign ? "ANN.cant.aug.assign.to.generator" : "ANN.cant.assign.to.generator"));
}
@Override
@@ -111,7 +110,7 @@ public class AssignTargetAnnotator extends PyAnnotator {
if (node.getElements().length == 0) {
getHolder().createErrorAnnotation(node, message("ANN.cant.assign.to.parens"));
}
- else if (_op == Operation.AugAssign) {
+ else if (myOp == Operation.AugAssign) {
getHolder().createErrorAnnotation(node, message("ANN.cant.aug.assign.to.tuple.or.generator"));
}
else {
@@ -121,7 +120,7 @@ public class AssignTargetAnnotator extends PyAnnotator {
@Override
public void visitPyParenthesizedExpression(final PyParenthesizedExpression node) {
- if (_op == Operation.AugAssign) {
+ if (myOp == Operation.AugAssign) {
getHolder().createErrorAnnotation(node, message("ANN.cant.aug.assign.to.tuple.or.generator"));
}
else {
@@ -134,7 +133,7 @@ public class AssignTargetAnnotator extends PyAnnotator {
if (node.getElements().length == 0) {
getHolder().createErrorAnnotation(node, message("ANN.cant.assign.to.brackets"));
}
- else if (_op == Operation.AugAssign) {
+ else if (myOp == Operation.AugAssign) {
getHolder().createErrorAnnotation(node, message("ANN.cant.aug.assign.to.list.or.comprh"));
}
else {
@@ -144,17 +143,17 @@ public class AssignTargetAnnotator extends PyAnnotator {
@Override
public void visitPyListCompExpression(final PyListCompExpression node) {
- markError(node, message(_op == Operation.AugAssign ? "ANN.cant.aug.assign.to.comprh" : "ANN.cant.assign.to.comprh"));
+ markError(node, message(myOp == Operation.AugAssign ? "ANN.cant.aug.assign.to.comprh" : "ANN.cant.assign.to.comprh"));
}
@Override
public void visitPyDictCompExpression(PyDictCompExpression node) {
- markError(node, message(_op == Operation.AugAssign ? "ANN.cant.aug.assign.to.dict.comprh" : "ANN.cant.assign.to.dict.comprh"));
+ markError(node, message(myOp == Operation.AugAssign ? "ANN.cant.aug.assign.to.dict.comprh" : "ANN.cant.assign.to.dict.comprh"));
}
@Override
public void visitPySetCompExpression(PySetCompExpression node) {
- markError(node, message(_op == Operation.AugAssign ? "ANN.cant.aug.assign.to.set.comprh" : "ANN.cant.assign.to.set.comprh"));
+ markError(node, message(myOp == Operation.AugAssign ? "ANN.cant.aug.assign.to.set.comprh" : "ANN.cant.assign.to.set.comprh"));
}
@Override
@@ -184,7 +183,7 @@ public class AssignTargetAnnotator extends PyAnnotator {
}
private void checkLiteral(PyExpression node) {
- getHolder().createErrorAnnotation(node, message(_op == Operation.Delete? "ANN.cant.delete.literal" : "ANN.cant.assign.to.literal"));
+ getHolder().createErrorAnnotation(node, message(myOp == Operation.Delete? "ANN.cant.delete.literal" : "ANN.cant.assign.to.literal"));
}
public void visitPyLambdaExpression(final PyLambdaExpression node) {
diff --git a/python/testData/completion/localVarInDictKey.after.py b/python/testData/completion/localVarInDictKey.after.py
new file mode 100644
index 000000000000..36099b4f8f74
--- /dev/null
+++ b/python/testData/completion/localVarInDictKey.after.py
@@ -0,0 +1,8 @@
+ENCODINGS = {}
+
+class C:
+ def foo(self):
+ if self.id[0] == 'T':
+ encoding = ord(self.rawData[0])
+ if 0 <= encoding < len(ENCODINGS):
+ value = self.rawData[1:].decode(ENCODINGS[encoding])
\ No newline at end of file
diff --git a/python/testData/completion/localVarInDictKey.py b/python/testData/completion/localVarInDictKey.py
new file mode 100644
index 000000000000..676c5e459a1e
--- /dev/null
+++ b/python/testData/completion/localVarInDictKey.py
@@ -0,0 +1,8 @@
+ENCODINGS = {}
+
+class C:
+ def foo(self):
+ if self.id[0] == 'T':
+ encoding = ord(self.rawData[0])
+ if 0 <= encoding < len(ENCODINGS):
+ value = self.rawData[1:].decode(ENCODINGS[enc])
\ No newline at end of file
diff --git a/python/testData/folding/classTrailingSpace.py b/python/testData/folding/classTrailingSpace.py
new file mode 100644
index 000000000000..aa6687209d17
--- /dev/null
+++ b/python/testData/folding/classTrailingSpace.py
@@ -0,0 +1,12 @@
+class Point:
+ def __init__(self, x=0, y=0):
+ self.x = x
+ self.y = y
+
+ def distance_from_origin(self):
+ return (self.x ** 2) + (self.y ** 2) ** 0.5
+
+# This is a comment
+p = Point(3,4)
+print "p.x = %d" % (p.x)
+print "p.y = %d" % (p.y)
\ No newline at end of file
diff --git a/python/testData/highlighting/assignmentTargets.py b/python/testData/highlighting/assignmentTargets.py
index 77aa61a34d1a..731541d68b73 100644
--- a/python/testData/highlighting/assignmentTargets.py
+++ b/python/testData/highlighting/assignmentTargets.py
@@ -6,6 +6,8 @@
1 + 21 = 12
+result = a < c and c = 4
+
() = 123
[] = 1
[1] = 1
diff --git a/python/testData/inspections/AugmentAssignmentWithContext.py b/python/testData/inspections/AugmentAssignmentWithContext.py
new file mode 100644
index 000000000000..e7a26bcccbb2
--- /dev/null
+++ b/python/testData/inspections/AugmentAssignmentWithContext.py
@@ -0,0 +1,4 @@
+class A:
+ x = 3
+a = A()
+a.x = a.x + 1
\ No newline at end of file
diff --git a/python/testData/inspections/AugmentAssignmentWithContext_after.py b/python/testData/inspections/AugmentAssignmentWithContext_after.py
new file mode 100644
index 000000000000..56470da44af6
--- /dev/null
+++ b/python/testData/inspections/AugmentAssignmentWithContext_after.py
@@ -0,0 +1,4 @@
+class A:
+ x = 3
+a = A()
+a.x += 1
\ No newline at end of file
diff --git a/python/testData/inspections/PyAugmentAssignmentInspection/test.py b/python/testData/inspections/PyAugmentAssignmentInspection/test.py
index bf91a1a9e638..fc807600fb9d 100644
--- a/python/testData/inspections/PyAugmentAssignmentInspection/test.py
+++ b/python/testData/inspections/PyAugmentAssignmentInspection/test.py
@@ -3,11 +3,28 @@
str = "string"
-var_2 = var_2 + str
+var_2 = var_2 + str
var_3 = var_3
var_4 = 1
-var_5 = var_5 + "string"
-var_6 = var_6 + var_4
+var_5 = var_5 + "string"
+var_6 = var_6 + var_4
-var_7 += 2
\ No newline at end of file
+var_7 += 2
+
+#PY-2482
+var = 2 + var
+
+# PY-2483
+list[0] = list[0] + 1
+
+# PY-2488
+a = a ** 1
+
+x = x % 3
+x = x | 3
+x = x & 3
+x = x ^ 3
+
+#PY-2514
+dy = 1 - dy
diff --git a/python/testData/inspections/PyChainedComparisonsInspection/test.py b/python/testData/inspections/PyChainedComparisonsInspection/test.py
index 491550c3abb2..027fb1ec9f7b 100644
--- a/python/testData/inspections/PyChainedComparisonsInspection/test.py
+++ b/python/testData/inspections/PyChainedComparisonsInspection/test.py
@@ -5,3 +5,7 @@ if a < b and b < c:
pass
q = a < c and c < d < e
a < c and b < e and e < f and a < b and b < c
+
+result = a < c and c == 4
+q = a < b < c and c <= d
+q = a >= b >= c and c > d
diff --git a/python/testData/inspections/PyDictDuplicateKeysInspection/test.py b/python/testData/inspections/PyDictDuplicateKeysInspection/test.py
index d8a5ace62bce..3fb6c0f73e84 100644
--- a/python/testData/inspections/PyDictDuplicateKeysInspection/test.py
+++ b/python/testData/inspections/PyDictDuplicateKeysInspection/test.py
@@ -7,4 +7,11 @@ import random
def foo():
return random.random()
-{foo(): 1, foo():2}
+{foo(): 1, foo():2}
+
+# PY-2511
+dict = dict([('key', 666), ('key', 123)])
+dict = dict((('key', 666), ('key', 123)))
+dict = dict((('key', 666), ('k', 123)), key=4)
+
+dict([('key', 666), ('ky', 123)])
diff --git a/python/testData/inspections/PyRedundantParenthesesInspection/test.py b/python/testData/inspections/PyRedundantParenthesesInspection/test.py
index 323771c90a65..13c61cabdc4f 100644
--- a/python/testData/inspections/PyRedundantParenthesesInspection/test.py
+++ b/python/testData/inspections/PyRedundantParenthesesInspection/test.py
@@ -24,4 +24,10 @@ if (A and
pass
if ("\n"):
- pass
\ No newline at end of file
+ pass
+
+result = (
+ "int line1 = 1;\n"
+ "\n"
+ "int line2 = 2;\n"
+)
\ No newline at end of file
diff --git a/python/testData/intentions/afterConvertLambdaToFunction.py b/python/testData/intentions/afterConvertLambdaToFunction.py
index 0cd0da9b151f..22f6ec568f7a 100644
--- a/python/testData/intentions/afterConvertLambdaToFunction.py
+++ b/python/testData/intentions/afterConvertLambdaToFunction.py
@@ -1,2 +1,3 @@
def newlist(x, y):
return (x + y) / y
+
diff --git a/python/testData/intentions/afterSplitIf.py b/python/testData/intentions/afterSplitIf.py
index 5a646512081e..4f82b8511f29 100644
--- a/python/testData/intentions/afterSplitIf.py
+++ b/python/testData/intentions/afterSplitIf.py
@@ -1,6 +1,7 @@
def foo():
if a + 2 > 3:
if b < 4:
+ #comment
a = a and b
b = 4
elif a > 20:
diff --git a/python/testData/intentions/beforeJoinIfElse.py b/python/testData/intentions/beforeJoinIfElse.py
new file mode 100644
index 000000000000..7aa6d903d092
--- /dev/null
+++ b/python/testData/intentions/beforeJoinIfElse.py
@@ -0,0 +1,7 @@
+def foo():
+ if a + 2 > 3:
+ if b < 4:
+ a = a and b
+ b = 4
+ else: # this prevents ifs from joining
+ print a
diff --git a/python/testData/intentions/beforeSplitIf.py b/python/testData/intentions/beforeSplitIf.py
index cd1f7877cc99..fc52062b94ff 100644
--- a/python/testData/intentions/beforeSplitIf.py
+++ b/python/testData/intentions/beforeSplitIf.py
@@ -1,5 +1,6 @@
def foo():
if a + 2 > 3 and b < 4:
+ #comment
a = a and b
b = 4
elif a > 20:
diff --git a/python/testData/override/typeAnnotations.py b/python/testData/override/typeAnnotations.py
new file mode 100644
index 000000000000..3865aa75edba
--- /dev/null
+++ b/python/testData/override/typeAnnotations.py
@@ -0,0 +1,8 @@
+class ArgsTest:
+ def __init__(self, key:str=None, value:str=None,
+ max_age=None, expires=None, path:str=None, domain:str=None,
+ secure:bool=False, httponly:bool=False, sync_expires:bool=True,
+ comment:str=None, version:int=None): pass
+
+class Sub(ArgsTest):
+ pass
diff --git a/python/testData/override/typeAnnotations_after.py b/python/testData/override/typeAnnotations_after.py
new file mode 100644
index 000000000000..e37787613501
--- /dev/null
+++ b/python/testData/override/typeAnnotations_after.py
@@ -0,0 +1,10 @@
+class ArgsTest:
+ def __init__(self, key:str=None, value:str=None,
+ max_age=None, expires=None, path:str=None, domain:str=None,
+ secure:bool=False, httponly:bool=False, sync_expires:bool=True,
+ comment:str=None, version:int=None): pass
+
+class Sub(ArgsTest):
+ def __init__(self, key:str=None, value:str=None, max_age=None, expires=None, path:str=None, domain:str=None,
+ secure:bool=False, httponly:bool=False, sync_expires:bool=True, comment:str=None, version:int=None):
+ super().__init__(key, value, max_age, expires, path, domain, secure, httponly, sync_expires, comment, version)
diff --git a/python/testData/stubs/AugAssignDunderAll.py b/python/testData/stubs/AugAssignDunderAll.py
new file mode 100644
index 000000000000..6c627416dfd7
--- /dev/null
+++ b/python/testData/stubs/AugAssignDunderAll.py
@@ -0,0 +1,4 @@
+__all__ = ['foo', 'bar']
+
+for i in range(5):
+ __all__ += 'f' + str(i)
diff --git a/python/testSrc/com/jetbrains/python/PyFoldingTest.java b/python/testSrc/com/jetbrains/python/PyFoldingTest.java
new file mode 100644
index 000000000000..3bb367837165
--- /dev/null
+++ b/python/testSrc/com/jetbrains/python/PyFoldingTest.java
@@ -0,0 +1,16 @@
+package com.jetbrains.python;
+
+import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
+
+/**
+ * @author yole
+ */
+public class PyFoldingTest extends PyLightFixtureTestCase {
+ private void doTest() {
+ myFixture.testFolding(getTestDataPath() + "/folding/" + getTestName(false) + ".py");
+ }
+
+ public void testClassTrailingSpace() { // PY-2544
+ doTest();
+ }
+}
diff --git a/python/testSrc/com/jetbrains/python/PyOverrideTest.java b/python/testSrc/com/jetbrains/python/PyOverrideTest.java
index ad56372ed28e..d489452d84a2 100644
--- a/python/testSrc/com/jetbrains/python/PyOverrideTest.java
+++ b/python/testSrc/com/jetbrains/python/PyOverrideTest.java
@@ -24,6 +24,16 @@ public class PyOverrideTest extends PyLightFixtureTestCase {
myFixture.checkResultByFile("override/" + getTestName(true) + "_after.py", true);
}
+ private void doTest3k() {
+ PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), LanguageLevel.PYTHON31);
+ try {
+ doTest();
+ }
+ finally {
+ PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), null);
+ }
+ }
+
private PyClass getTopLevelClass(int index) {
PyFile file = (PyFile) myFixture.getFile();
return file.getTopLevelClasses().get(index);
@@ -65,12 +75,11 @@ public class PyOverrideTest extends PyLightFixtureTestCase {
}
public void testPy3k() {
- PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), LanguageLevel.PYTHON31);
- try {
- doTest();
- }
- finally {
- PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), null);
- }
+ doTest3k();
+ }
+
+
+ public void testTypeAnnotations() { // PY-2547
+ doTest3k();
}
}
diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java
index bc9f47b6e55c..e28884bb3703 100644
--- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java
+++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java
@@ -179,6 +179,11 @@ public class PyQuickFixTest extends PyLightFixtureTestCase {
PyBundle.message("QFIX.augment.assignment"), true, true);
}
+ public void testAugmentAssignmentWithContext() { // PY-2481
+ doInspectionTest("AugmentAssignmentWithContext.py", PyAugmentAssignmentInspection.class,
+ PyBundle.message("QFIX.augment.assignment"), true, true);
+ }
+
public void testChainedComparisons() { // PY-1020
doInspectionTest("ChainedComparisons.py", PyChainedComparisonsInspection.class,
PyBundle.message("QFIX.chained.comparison"), true, true);
diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java
index e847b90ffee9..dc649f6f2c43 100644
--- a/python/testSrc/com/jetbrains/python/PyStubsTest.java
+++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java
@@ -241,6 +241,13 @@ public class PyStubsTest extends PyLightFixtureTestCase {
assertNotParsed(file);
}
+ public void testAugAssignDunderAll() {
+ final PyFileImpl file = (PyFileImpl) getTestFile();
+ final List all = file.getDunderAll();
+ assertNull(all);
+ assertNotParsed(file);
+ }
+
public void testSlots() {
final PyFileImpl file = (PyFileImpl) getTestFile();
final PyClass pyClass = file.getTopLevelClasses().get(0);
diff --git a/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java b/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java
index 297e6f7698e5..5996aa72f793 100644
--- a/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java
+++ b/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java
@@ -63,7 +63,8 @@ public class PythonAllTestsSuite {
PySuppressInspectionsTest.class,
PyPropertyTestSuite.PyClassicPropertyTest.class,
PyPropertyTestSuite.PyDecoratedPropertyTest.class,
- PythonRunConfigurationTest.class
+ PythonRunConfigurationTest.class,
+ PyFoldingTest.class
};
public static TestSuite suite() {
diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java
index 578906694157..54c61f7f671b 100644
--- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java
+++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java
@@ -247,4 +247,8 @@ public class PythonCompletionTest extends PyLightFixtureTestCase {
public void testElseInCondExpr() { // PY-2397
doTest();
}
+
+ public void testLocalVarInDictKey() { // PY-2558
+ doTest();
+ }
}