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 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(); + } }