summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/edu/build/pycharm_edu_build.gant8
-rw-r--r--python/edu/build/upload_pythonInfo.xml2
-rw-r--r--python/edu/course-creator/resources/META-INF/plugin.xml14
-rw-r--r--python/edu/course-creator/resources/fileTemplates/internal/task.answer.ft1
-rw-r--r--python/edu/course-creator/resources/fileTemplates/internal/task.py.ft1
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileType.java40
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileTypeFactory.java28
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java13
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java141
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java52
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRefactoringElementListenerProvider.java96
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTests.java284
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/RunTestsLineMarker.java109
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java5
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java5
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRename.java97
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameLesson.java48
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameTask.java57
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCShowPreview.java104
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java188
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java14
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java6
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java14
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java31
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java20
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java27
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java11
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java2
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java18
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form2
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java6
-rw-r--r--python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java5
-rw-r--r--python/edu/learn-python/resources/META-INF/plugin.xml7
-rw-r--r--python/edu/learn-python/resources/courses/introduction_course.zipbin93557 -> 95274 bytes
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java2
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java4
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyIntroductionCourseAction.java75
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java34
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java211
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyReloadCourseAction.java134
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java7
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java55
-rw-r--r--python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java11
-rw-r--r--python/edu/resources/idea/PyCharmEduApplicationInfo.xml2
-rw-r--r--python/helpers/packaging_tool.py14
-rw-r--r--python/helpers/pycharm/_bdd_utils.py37
-rw-r--r--python/helpers/pycharm/behave_runner.py38
-rw-r--r--python/helpers/pycharm/lettuce_runner.py51
-rw-r--r--python/helpers/pycharm_generator_utils/clr_tools.py13
-rw-r--r--python/helpers/pydev/django_debug.py124
-rw-r--r--python/helpers/pydev/django_frame.py132
-rw-r--r--python/helpers/pydev/pydev_log.py9
-rw-r--r--python/helpers/pydev/pydev_monkey_qt.py7
-rw-r--r--python/helpers/pydev/pydev_run_in_console.py19
-rw-r--r--python/helpers/pydev/pydevconsole.py13
-rw-r--r--python/helpers/pydev/pydevd.py185
-rw-r--r--python/helpers/pydev/pydevd_breakpoints.py2
-rw-r--r--python/helpers/pydev/pydevd_constants.py1
-rw-r--r--python/helpers/pydev/pydevd_frame.py274
-rw-r--r--python/helpers/pydev/pydevd_frame_utils.py31
-rw-r--r--python/helpers/pydev/pydevd_plugin_utils.py85
-rw-r--r--python/helpers/pydev/pydevd_plugins/__init__.py0
-rw-r--r--python/helpers/pydev/pydevd_plugins/django_debug.py357
-rw-r--r--python/helpers/pydev/pydevd_plugins/jinja2_debug.py341
-rw-r--r--python/helpers/pydev/pydevd_resolver.py14
-rw-r--r--python/helpers/pydev/pydevd_trace_api.py35
-rw-r--r--python/helpers/pydev/pydevd_utils.py23
-rw-r--r--python/helpers/pydev/pydevd_vars.py11
-rw-r--r--python/helpers/pydev/test_debug.py4
-rw-r--r--python/helpers/pydev/tests/check_pydevconsole.py110
-rw-r--r--python/helpers/pydev/tests/test_pydev_ipython_010.py80
-rw-r--r--python/helpers/pydev/tests_python/_debugger_case_qthread3.py1
-rw-r--r--python/helpers/pydev/third_party/pkgutil_old.py591
-rw-r--r--python/helpers/pydev/third_party/pluginbase.py454
-rw-r--r--python/helpers/pydev/third_party/uuid_old.py541
-rw-r--r--python/ide/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java18
-rw-r--r--python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java74
-rw-r--r--python/ide/src/com/jetbrains/python/newProject/actions/GenerateProjectCallback.java2
-rw-r--r--python/openapi/src/com/jetbrains/python/documentation/PythonDocumentationQuickInfoProvider.java25
-rw-r--r--python/openapi/src/com/jetbrains/python/packaging/PyPackageManager.java42
-rw-r--r--python/openapi/src/com/jetbrains/python/packaging/PyPackageManagers.java22
-rw-r--r--python/openapi/src/com/jetbrains/python/templateLanguages/PyTemplatesUtil.java4
-rw-r--r--python/psi-api/src/com/jetbrains/python/PyNames.java2
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java6
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java53
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java3
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/PyReferrersLoader.java20
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/PyReferringObjectsValue.java71
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/PyThreadInfo.java2
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/PyTypeHandler.java3
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java12
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/ConsoleExecCommand.java2
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/GetReferrersCommand.java50
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/GetVariableCommand.java32
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java12
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java17
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java26
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/PyDebugCallback.java12
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/PyVariableLocator.java17
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java50
-rw-r--r--python/pydevSrc/com/jetbrains/python/debugger/pydev/RunCustomOperationCommand.java86
-rw-r--r--python/python-rest/src/com/jetbrains/rest/RestPythonUtil.java5
-rw-r--r--python/src/META-INF/pycharm-core.xml8
-rw-r--r--python/src/META-INF/python-core.xml25
-rw-r--r--python/src/com/jetbrains/python/PyBundle.properties3
-rw-r--r--python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java17
-rw-r--r--python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java31
-rw-r--r--python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java10
-rw-r--r--python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java2
-rw-r--r--python/src/com/jetbrains/python/console/PydevConsoleCommunication.java10
-rw-r--r--python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java8
-rw-r--r--python/src/com/jetbrains/python/debugger/PyDebugProcess.java28
-rw-r--r--python/src/com/jetbrains/python/debugger/PyDebugSupportUtils.java22
-rw-r--r--python/src/com/jetbrains/python/debugger/PyExceptionBreakpointProperties.java5
-rw-r--r--python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java14
-rw-r--r--python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java4
-rw-r--r--python/src/com/jetbrains/python/hierarchy/PyHierarchyNodeDescriptor.java (renamed from python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyNodeDescriptor.java)31
-rw-r--r--python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyBrowser.java4
-rw-r--r--python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java101
-rw-r--r--python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java76
-rw-r--r--python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyTreeStructureBase.java85
-rw-r--r--python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java42
-rw-r--r--python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java42
-rw-r--r--python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java163
-rw-r--r--python/src/com/jetbrains/python/hierarchy/treestructures/PySubTypesHierarchyTreeStructure.java18
-rw-r--r--python/src/com/jetbrains/python/hierarchy/treestructures/PySuperTypesHierarchyTreeStructure.java25
-rw-r--r--python/src/com/jetbrains/python/hierarchy/treestructures/PyTypeHierarchyTreeStructure.java27
-rw-r--r--python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java97
-rw-r--r--python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java2
-rw-r--r--python/src/com/jetbrains/python/inspections/quickfix/PyDefaultArgumentQuickFix.java2
-rw-r--r--python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java21
-rw-r--r--python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java5
-rw-r--r--python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java665
-rw-r--r--python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java367
-rw-r--r--python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java24
-rw-r--r--python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java177
-rw-r--r--python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java119
-rw-r--r--python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java15
-rw-r--r--python/src/com/jetbrains/python/projectView/PyElementNode.java24
-rw-r--r--python/src/com/jetbrains/python/psi/PyUtil.java4
-rw-r--r--python/src/com/jetbrains/python/psi/impl/PyClassImpl.java32
-rw-r--r--python/src/com/jetbrains/python/psi/impl/PyElementPresentation.java76
-rw-r--r--python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java84
-rw-r--r--python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java8
-rw-r--r--python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java73
-rw-r--r--python/src/com/jetbrains/python/psi/impl/PySingleStarParameterImpl.java20
-rw-r--r--python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java2
-rw-r--r--python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java22
-rw-r--r--python/src/com/jetbrains/python/psi/impl/PyTupleParameterImpl.java15
-rw-r--r--python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java5
-rw-r--r--python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java2
-rw-r--r--python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionDelegate.java3
-rw-r--r--python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java6
-rw-r--r--python/src/com/jetbrains/python/sdk/PySdkUtil.java153
-rw-r--r--python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java6
-rw-r--r--python/src/com/jetbrains/python/sdk/PythonSdkType.java26
-rw-r--r--python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java3
-rw-r--r--python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java33
-rw-r--r--python/src/com/jetbrains/python/sdk/skeletons/PySkeletonGenerator.java39
-rw-r--r--python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java152
-rw-r--r--python/src/com/jetbrains/python/statistics/PyPackageUsagesCollector.java6
-rw-r--r--python/src/com/jetbrains/python/structureView/PyStructureViewElement.java33
-rw-r--r--python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java33
-rw-r--r--python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java4
-rw-r--r--python/src/com/jetbrains/python/testing/TestRunConfigurationReRunResponsible.java30
-rw-r--r--python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java5
-rw-r--r--python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java5
-rw-r--r--python/src/com/jetbrains/python/validation/Pep8ExternalAnnotator.java3
-rw-r--r--python/src/com/jetbrains/python/validation/StringLiteralQuotesAnnotator.java45
-rw-r--r--python/testData/codeInsight/smartEnter/withOnlyColonMissing.py1
-rw-r--r--python/testData/codeInsight/smartEnter/withOnlyColonMissing_after.py2
-rw-r--r--python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml1
-rw-r--r--python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml1
-rw-r--r--python/testData/hierarchy/call/Static/ArgumentList/file_1.py8
-rw-r--r--python/testData/hierarchy/call/Static/ArgumentList/main.py8
-rw-r--r--python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml8
-rw-r--r--python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml6
-rw-r--r--python/testData/hierarchy/call/Static/Constructor/main.py32
-rw-r--r--python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml1
-rw-r--r--python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml4
-rw-r--r--python/testData/hierarchy/call/Static/DefaultValue/main.py30
-rw-r--r--python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml1
-rw-r--r--python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml4
-rw-r--r--python/testData/hierarchy/call/Static/Inheritance/main.py36
-rw-r--r--python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml5
-rw-r--r--python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml1
-rw-r--r--python/testData/hierarchy/call/Static/InnerFunction/main.py9
-rw-r--r--python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml8
-rw-r--r--python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml1
-rw-r--r--python/testData/hierarchy/call/Static/Lambda/file_1.py18
-rw-r--r--python/testData/hierarchy/call/Static/Lambda/main.py18
-rw-r--r--python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml12
-rw-r--r--python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml1
-rw-r--r--python/testData/hierarchy/call/Static/NestedCall/file_1.py10
-rw-r--r--python/testData/hierarchy/call/Static/NestedCall/main.py10
-rw-r--r--python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml3
-rw-r--r--python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml5
-rw-r--r--python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py8
-rw-r--r--python/testData/hierarchy/call/Static/OverriddenMethod/main.py29
-rw-r--r--python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml3
-rw-r--r--python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml3
-rw-r--r--python/testData/hierarchy/call/Static/Parentheses/file_1.py20
-rw-r--r--python/testData/hierarchy/call/Static/Parentheses/main.py7
-rw-r--r--python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml3
-rw-r--r--python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml3
-rw-r--r--python/testData/hierarchy/call/Static/Simple/main.py10
-rw-r--r--python/testData/highlighting/multipleEscapedBackslashes.py2
-rw-r--r--python/testData/inspections/DefaultArgumentEmptyList.py2
-rw-r--r--python/testData/inspections/DefaultArgumentEmptyList_after.py2
-rw-r--r--python/testData/mover/outsideFromDict.py5
-rw-r--r--python/testData/mover/outsideFromDict_afterDown.py5
-rw-r--r--python/testData/mover/outsideFromDict_afterUp.py5
-rw-r--r--python/testData/mover/sameLevelAsDict.py9
-rw-r--r--python/testData/mover/sameLevelAsDict_afterDown.py9
-rw-r--r--python/testData/mover/sameLevelAsDict_afterUp.py9
-rw-r--r--python/testSrc/com/jetbrains/env/python/PyPackagingTest.java18
-rw-r--r--python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java21
-rw-r--r--python/testSrc/com/jetbrains/env/ut/PyUnitTestTask.java108
-rw-r--r--python/testSrc/com/jetbrains/python/PySmartEnterTest.java5
-rw-r--r--python/testSrc/com/jetbrains/python/PyStatementMoverTest.java8
-rw-r--r--python/testSrc/com/jetbrains/python/PythonHighlightingTest.java4
-rw-r--r--python/testSrc/com/jetbrains/python/PythonKeywordCompletionTest.java11
-rw-r--r--python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java66
-rw-r--r--python/testSrc/com/jetbrains/python/hierarchy/PyCallHierarchyTest.java158
-rw-r--r--python/testSrc/com/jetbrains/python/refactoring/PyInlineLocalTest.java16
-rw-r--r--python/testSrc/com/jetbrains/python/sdkTools/PyTestSdkTools.java2
226 files changed, 7945 insertions, 2353 deletions
diff --git a/python/edu/build/pycharm_edu_build.gant b/python/edu/build/pycharm_edu_build.gant
index 28d829e68b34..356df8c7d4c7 100644
--- a/python/edu/build/pycharm_edu_build.gant
+++ b/python/edu/build/pycharm_edu_build.gant
@@ -158,7 +158,6 @@ public layoutEducational(String classesPath, Set usedJars) {
usedJars = collectUsedJars(modules(), approvedJars(), ["/ant/"], null)
}
- def appInfo = appInfoFile()
def paths = new Paths(home)
buildSearchableOptions("${projectBuilder.moduleOutput(findModule("platform-resources"))}/search", [], {
projectBuilder.moduleRuntimeClasspath(findModule("main_pycharm_edu"), false).each {
@@ -166,8 +165,9 @@ public layoutEducational(String classesPath, Set usedJars) {
}
}, "-Didea.platform.prefix=PyCharmEdu -Didea.no.jre.check=true")
+ def appInfo = appInfoFile()
if (!dryRun) {
- wireBuildDate("PE-${buildNumber}", appInfo)
+ wireBuildDate(buildNumber, appInfo)
}
Map args = [
@@ -223,7 +223,7 @@ private layoutPlugins(layouts) {
}
private String appInfoFile() {
- return "$pythonEduHome/resources/idea/PyCharmEduApplicationInfo.xml"
+ return "$home/out/pycharmEDU/classes/production/python-educational/idea/PyCharmEduApplicationInfo.xml"
}
private layoutFull(Map args, String target, Set usedJars) {
@@ -405,7 +405,7 @@ private layoutMac(Map _args, String target) {
args.icns = "$pythonCommunityHome/resources/PyCharmCore.icns"
args.bundleIdentifier = "com.jetbrains.pycharm"
args.platform_prefix = "PyCharmEdu"
- args.help_id = "PY"
+ args.help_id = "PE"
args."idea.properties.path" = "${paths.distAll}/bin/idea.properties"
args."idea.properties" = ["idea.no.jre.check": true, "ide.mac.useNativeClipboard": "false"];
layoutMacApp(target, ch, args)
diff --git a/python/edu/build/upload_pythonInfo.xml b/python/edu/build/upload_pythonInfo.xml
index f8d9477d1a3f..0633ad82f8c0 100644
--- a/python/edu/build/upload_pythonInfo.xml
+++ b/python/edu/build/upload_pythonInfo.xml
@@ -20,7 +20,7 @@
</fileset>
<fileset dir="${home}/community/lib">
<include name="commons-net-3.1.jar"/>
- <include name="jsch-0.1.50.jar"/>
+ <include name="jsch-0.1.51.jar"/>
</fileset>
</classpath>
diff --git a/python/edu/course-creator/resources/META-INF/plugin.xml b/python/edu/course-creator/resources/META-INF/plugin.xml
index 6a9a9ea90276..0e7f75c90051 100644
--- a/python/edu/course-creator/resources/META-INF/plugin.xml
+++ b/python/edu/course-creator/resources/META-INF/plugin.xml
@@ -26,6 +26,10 @@
<codeInsight.lineMarkerProvider language="Python"
implementationClass="org.jetbrains.plugins.coursecreator.highlighting.CCTaskLineMarkerProvider"/>
<treeStructureProvider implementation="org.jetbrains.plugins.coursecreator.projectView.CCTreeStructureProvider"/>
+ <codeInsight.lineMarkerProvider language="Python" implementationClass="org.jetbrains.plugins.coursecreator.RunTestsLineMarker"/>
+ <fileTypeFactory implementation="org.jetbrains.plugins.coursecreator.AnswerFileTypeFactory" />
+ <refactoring.elementListenerProvider implementation="org.jetbrains.plugins.coursecreator.CCRefactoringElementListenerProvider"/>
+ <errorHandler implementation="com.intellij.diagnostic.ITNReporter"/>
</extensions>
<application-components>
@@ -52,9 +56,19 @@
<action id="AddTaskWindow" class="org.jetbrains.plugins.coursecreator.actions.AddTaskWindow">
<add-to-group group-id="EditorPopupMenu" anchor="before" relative-to-action="CopyReference"/>
</action>
+ <action id="ShowPreview" class="org.jetbrains.plugins.coursecreator.actions.CCShowPreview">
+ <add-to-group group-id="ProjectViewPopupMenu" anchor="first"/>
+ </action>
+ <action id="RenameLesson" class="org.jetbrains.plugins.coursecreator.actions.CCRenameLesson">
+ <add-to-group group-id="ProjectViewPopupMenu" anchor="before" relative-to-action="CutCopyPasteGroup"/>
+ </action>
+ <action id="RenameTask" class="org.jetbrains.plugins.coursecreator.actions.CCRenameTask">
+ <add-to-group group-id="ProjectViewPopupMenu" anchor="before" relative-to-action="CutCopyPasteGroup"/>
+ </action>
<action id="PackCourse" class="org.jetbrains.plugins.coursecreator.actions.CreateCourseArchive">
<add-to-group group-id="MainToolBar" anchor="last" />
</action>
+ <action id="CCRunTests" class="org.jetbrains.plugins.coursecreator.CCRunTests" text="Run tests" description="Run tests"/>
</actions>
</idea-plugin> \ No newline at end of file
diff --git a/python/edu/course-creator/resources/fileTemplates/internal/task.answer.ft b/python/edu/course-creator/resources/fileTemplates/internal/task.answer.ft
new file mode 100644
index 000000000000..f0a4bcecdd24
--- /dev/null
+++ b/python/edu/course-creator/resources/fileTemplates/internal/task.answer.ft
@@ -0,0 +1 @@
+# TODO: type solution here
diff --git a/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft b/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft
deleted file mode 100644
index 0256e108786c..000000000000
--- a/python/edu/course-creator/resources/fileTemplates/internal/task.py.ft
+++ /dev/null
@@ -1 +0,0 @@
-# TODO: type solution here \ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileType.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileType.java
new file mode 100644
index 000000000000..3b8189aadffc
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileType.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator;
+
+import com.jetbrains.python.PythonFileType;
+import org.jetbrains.annotations.NotNull;
+
+public class AnswerFileType extends PythonFileType {
+
+ @NotNull
+ @Override
+ public String getDefaultExtension() {
+ return "answer";
+ }
+
+ @NotNull
+ @Override
+ public String getDescription() {
+ return "Answer file";
+ }
+
+ @NotNull
+ @Override
+ public String getName() {
+ return "Answer";
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileTypeFactory.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileTypeFactory.java
new file mode 100644
index 000000000000..e4adcbc5b386
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/AnswerFileTypeFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator;
+
+
+import com.intellij.openapi.fileTypes.FileTypeConsumer;
+import com.intellij.openapi.fileTypes.FileTypeFactory;
+import org.jetbrains.annotations.NotNull;
+
+public class AnswerFileTypeFactory extends FileTypeFactory {
+ @Override
+ public void createFileTypes(@NotNull FileTypeConsumer fileTypeConsumer) {
+ fileTypeConsumer.consume(AnswerFileType.INSTANCE, "answer");
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java
index 1eb8690aad22..acb0d43d0d12 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCEditorFactoryListener.java
@@ -35,6 +35,9 @@ public class CCEditorFactoryListener implements EditorFactoryListener {
final Lesson lesson = course.getLesson(lessonDir.getName());
final Task task = lesson.getTask(taskDir.getName());
final TaskFile taskFile = task.getTaskFile(virtualFile.getName());
+ if (taskFile == null) {
+ return;
+ }
TaskFileModificationListener listener = new TaskFileModificationListener(taskFile);
CCProjectService.addDocumentListener(editor.getDocument(), listener);
editor.getDocument().addDocumentListener(listener);
@@ -54,13 +57,10 @@ public class CCEditorFactoryListener implements EditorFactoryListener {
editor.getSelectionModel().removeSelection();
}
- private class TaskFileModificationListener extends StudyDocumentListener {
-
- private final TaskFile myTaskFile;
+ private static class TaskFileModificationListener extends StudyDocumentListener {
public TaskFileModificationListener(TaskFile taskFile) {
super(taskFile);
- myTaskFile = taskFile;
}
@Override
@@ -71,10 +71,5 @@ public class CCEditorFactoryListener implements EditorFactoryListener {
taskWindow.setReplacementLength(taskWindow.getLength() + 1);
}
}
-
- @Override
- protected boolean needModify() {
- return myTaskFile.isTrackChanges();
- }
}
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java
index 34de943d2ad0..8b524dbffd4b 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectComponent.java
@@ -1,22 +1,33 @@
package org.jetbrains.plugins.coursecreator;
+import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ProjectComponent;
+import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.EditorFactoryEvent;
import com.intellij.openapi.editor.impl.EditorFactoryImpl;
-import com.intellij.openapi.fileEditor.FileEditor;
-import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileAdapter;
+import com.intellij.openapi.vfs.VirtualFileEvent;
+import com.intellij.openapi.vfs.VirtualFileManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.coursecreator.format.Course;
+import org.jetbrains.plugins.coursecreator.format.Lesson;
+import org.jetbrains.plugins.coursecreator.format.Task;
+import org.jetbrains.plugins.coursecreator.format.TaskFile;
+
+import java.io.IOException;
public class CCProjectComponent implements ProjectComponent {
+ private static final Logger LOG = Logger.getInstance(CCProjectComponent.class.getName());
private final Project myProject;
+ private FileDeletedListener myListener;
public CCProjectComponent(Project project) {
myProject = project;
@@ -37,15 +48,46 @@ public class CCProjectComponent implements ProjectComponent {
StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new Runnable() {
@Override
public void run() {
- Course course = CCProjectService.getInstance(myProject).getCourse();
+ final Course course = CCProjectService.getInstance(myProject).getCourse();
if (course != null) {
- EditorFactory.getInstance().addEditorFactoryListener(new CCEditorFactoryListener(), myProject);
+ myProject.getMessageBus().connect(myProject).subscribe(
+ FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerAdapter() {
+ @Override
+ public void selectionChanged(@NotNull FileEditorManagerEvent event) {
+ final VirtualFile oldFile = event.getOldFile();
+ if (oldFile == null) {
+ return;
+ }
+ if (CCProjectService.getInstance(myProject).isTaskFile(oldFile)) {
+ FileEditorManager.getInstance(myProject).closeFile(oldFile);
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ oldFile.delete(myProject);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ });
+ }
+ }
+ });
+ myListener = new FileDeletedListener();
+ VirtualFileManager.getInstance().addVirtualFileListener(myListener);
+ final CCEditorFactoryListener editorFactoryListener = new CCEditorFactoryListener();
+ EditorFactory.getInstance().addEditorFactoryListener(editorFactoryListener, myProject);
VirtualFile[] files = FileEditorManager.getInstance(myProject).getOpenFiles();
for (VirtualFile file : files) {
+ if (CCProjectService.getInstance(myProject).isTaskFile(file)) {
+ FileEditorManager.getInstance(myProject).closeFile(file);
+ continue;
+ }
FileEditor fileEditor = FileEditorManager.getInstance(myProject).getSelectedEditor(file);
if (fileEditor instanceof PsiAwareTextEditorImpl) {
Editor editor = ((PsiAwareTextEditorImpl)fileEditor).getEditor();
- new CCEditorFactoryListener().editorCreated(new EditorFactoryEvent(new EditorFactoryImpl(ProjectManager.getInstance()), editor ));
+ editorFactoryListener.editorCreated(new EditorFactoryEvent(new EditorFactoryImpl(ProjectManager.getInstance()), editor));
}
}
}
@@ -54,5 +96,94 @@ public class CCProjectComponent implements ProjectComponent {
}
public void projectClosed() {
+ if (myListener != null) {
+ VirtualFileManager.getInstance().removeVirtualFileListener(myListener);
+ }
+ }
+
+ private class FileDeletedListener extends VirtualFileAdapter {
+
+ @Override
+ public void fileDeleted(@NotNull VirtualFileEvent event) {
+ if (myProject.isDisposed() || !myProject.isOpen()) {
+ return;
+ }
+ Course course = CCProjectService.getInstance(myProject).getCourse();
+ if (course == null) {
+ return;
+ }
+ VirtualFile removedFile = event.getFile();
+ if (removedFile.getName().contains(".answer")) {
+ deleteTaskFile(course, removedFile);
+ }
+ if (removedFile.getName().contains("task")) {
+ deleteTask(course, removedFile);
+ }
+ if (removedFile.getName().contains("lesson")) {
+ deleteLesson(course, removedFile);
+ }
+ }
+
+ private void deleteLesson(Course course, VirtualFile file) {
+ VirtualFile courseDir = file.getParent();
+ if (!courseDir.getName().equals(myProject.getName())) {
+ return;
+ }
+ Lesson lesson = course.getLesson(file.getName());
+ if (lesson != null) {
+ course.getLessons().remove(lesson);
+ course.getLessonsMap().remove(file.getName());
+ }
+ }
+
+ private void deleteTask(Course course, VirtualFile removedFile) {
+ VirtualFile lessonDir = removedFile.getParent();
+ if (lessonDir == null || !lessonDir.getName().contains("lesson")) {
+ return;
+ }
+ VirtualFile courseDir = lessonDir.getParent();
+ if (!courseDir.getName().equals(myProject.getName())) {
+ return;
+ }
+ Lesson lesson = course.getLesson(lessonDir.getName());
+ if (lesson == null) {
+ return;
+ }
+ Task task = lesson.getTask(removedFile.getName());
+ if (task == null) {
+ return;
+ }
+ lesson.getTaskList().remove(task);
+ lesson.getTasksMap().remove(removedFile.getName());
+ }
+
+ private void deleteTaskFile(Course course, VirtualFile removedFile) {
+ VirtualFile taskDir = removedFile.getParent();
+ if (taskDir == null || !taskDir.getName().contains("task")) {
+ return;
+ }
+ VirtualFile lessonDir = taskDir.getParent();
+ if (lessonDir == null || !lessonDir.getName().contains("lesson")) {
+ return;
+ }
+ VirtualFile courseDir = lessonDir.getParent();
+ if (!courseDir.getName().equals(myProject.getName())) {
+ return;
+ }
+ Lesson lesson = course.getLesson(lessonDir.getName());
+ if (lesson == null) {
+ return;
+ }
+ Task task = lesson.getTask(taskDir.getName());
+ if (task == null) {
+ return;
+ }
+ TaskFile taskFile = task.getTaskFile(removedFile.getName());
+ if (taskFile == null) {
+ return;
+ }
+ String name = CCProjectService.getRealTaskFileName(removedFile.getName());
+ task.getTaskFiles().remove(name);
+ }
}
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java
index 1e38bab865cb..c06925e9a453 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCProjectService.java
@@ -32,6 +32,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.coursecreator.format.*;
import java.io.File;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -70,6 +71,9 @@ public class CCProjectService implements PersistentStateComponent<Element> {
@Override
public void loadState(Element el) {
myCourse = XmlSerializer.deserialize(el.getChild(COURSE_ELEMENT), Course.class);
+ if (myCourse != null) {
+ myCourse.init();
+ }
}
public static CCProjectService getInstance(@NotNull Project project) {
@@ -115,7 +119,7 @@ public class CCProjectService implements PersistentStateComponent<Element> {
}
List<TaskWindow> taskWindows = taskFile.getTaskWindows();
for (TaskWindow taskWindow : taskWindows) {
- taskWindow.drawHighlighter(editor);
+ taskWindow.drawHighlighter(editor, false);
}
}
@@ -131,8 +135,52 @@ public class CCProjectService implements PersistentStateComponent<Element> {
myDocumentListeners.remove(document);
}
- public static boolean indexIsValid(int index, List<TaskWindow> collection) {
+ public static boolean indexIsValid(int index, Collection collection) {
int size = collection.size();
return index >= 0 && index < size;
}
+
+ public boolean isTaskFile(VirtualFile file) {
+ if (myCourse == null || file == null) {
+ return false;
+ }
+ VirtualFile taskDir = file.getParent();
+ if (taskDir != null) {
+ String taskDirName = taskDir.getName();
+ if (taskDirName.contains("task")) {
+ VirtualFile lessonDir = taskDir.getParent();
+ if (lessonDir != null) {
+ String lessonDirName = lessonDir.getName();
+ int lessonIndex = getIndex(lessonDirName, "lesson");
+ List<Lesson> lessons = myCourse.getLessons();
+ if (!indexIsValid(lessonIndex, lessons)) {
+ return false;
+ }
+ Lesson lesson = lessons.get(lessonIndex);
+ int taskIndex = getIndex(taskDirName, "task");
+ List<Task> tasks = lesson.getTaskList();
+ if (!indexIsValid(taskIndex, tasks)) {
+ return false;
+ }
+ Task task = tasks.get(taskIndex);
+ return task.isTaskFile(file.getName());
+ }
+ }
+ }
+ return false;
+ }
+
+ public static int getIndex(@NotNull final String fullName, @NotNull final String logicalName) {
+ if (!fullName.contains(logicalName)) {
+ throw new IllegalArgumentException();
+ }
+ return Integer.parseInt(fullName.substring(logicalName.length())) - 1;
+ }
+ public static String getRealTaskFileName(String name) {
+ if (!name.contains(".answer")) {
+ return null;
+ }
+ int nameEnd = name.indexOf(".answer");
+ return name.substring(0, nameEnd) + ".py";
+ }
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRefactoringElementListenerProvider.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRefactoringElementListenerProvider.java
new file mode 100644
index 000000000000..601e4fc7cb40
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRefactoringElementListenerProvider.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator;
+
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.refactoring.listeners.RefactoringElementAdapter;
+import com.intellij.refactoring.listeners.RefactoringElementListener;
+import com.intellij.refactoring.listeners.RefactoringElementListenerProvider;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.format.Course;
+import org.jetbrains.plugins.coursecreator.format.Lesson;
+import org.jetbrains.plugins.coursecreator.format.Task;
+import org.jetbrains.plugins.coursecreator.format.TaskFile;
+
+import java.util.Map;
+
+public class CCRefactoringElementListenerProvider implements RefactoringElementListenerProvider {
+ @Nullable
+ @Override
+ public RefactoringElementListener getListener(PsiElement element) {
+ return new CCRenameListener(element);
+ }
+
+
+ static class CCRenameListener extends RefactoringElementAdapter {
+
+ private String myElementName;
+
+ public CCRenameListener(PsiElement element) {
+ if (element instanceof PsiFile) {
+ PsiFile psiFile = (PsiFile)element;
+ myElementName = psiFile.getName();
+ }
+ }
+
+ @Override
+ protected void elementRenamedOrMoved(@NotNull PsiElement newElement) {
+ if (newElement instanceof PsiFile && myElementName != null) {
+ PsiFile psiFile = (PsiFile)newElement;
+ if (myElementName.contains(".answer")) {
+ //this is task file
+ renameTaskFile(psiFile, myElementName);
+ }
+ }
+ }
+
+ private static void renameTaskFile(PsiFile file, String oldName) {
+ PsiDirectory taskDir = file.getContainingDirectory();
+ Course course = CCProjectService.getInstance(file.getProject()).getCourse();
+ if (course == null) {
+ return;
+ }
+ if (taskDir == null || !taskDir.getName().contains("task")) {
+ return;
+ }
+ PsiDirectory lessonDir = taskDir.getParent();
+ if (lessonDir == null || !lessonDir.getName().contains("lesson")) {
+ return;
+ }
+ Lesson lesson = course.getLesson(lessonDir.getName());
+ if (lesson == null) {
+ return;
+ }
+ Task task = lesson.getTask(taskDir.getName());
+ if (task == null) {
+ return;
+ }
+ Map<String, TaskFile> taskFiles = task.getTaskFiles();
+ TaskFile taskFile = task.getTaskFile(oldName);
+ String realTaskFileName = CCProjectService.getRealTaskFileName(oldName);
+ taskFiles.remove(realTaskFileName);
+ taskFiles.put(CCProjectService.getRealTaskFileName(file.getName()), taskFile);
+ }
+
+ @Override
+ public void undoElementMovedOrRenamed(@NotNull PsiElement newElement, @NotNull String oldQualifiedName) {
+
+ }
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTests.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTests.java
new file mode 100644
index 000000000000..d078972e3f56
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/CCRunTests.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator;
+
+import com.intellij.execution.*;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.executors.DefaultRunExecutor;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.containers.HashMap;
+import com.jetbrains.python.run.PythonConfigurationType;
+import com.jetbrains.python.run.PythonRunConfiguration;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.plugins.coursecreator.actions.CreateCourseArchive;
+import org.jetbrains.plugins.coursecreator.format.*;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+
+public class CCRunTests extends AnAction {
+ private static final Logger LOG = Logger.getInstance(CCRunTests.class.getName());
+
+ public CCRunTests() {
+ getTemplatePresentation().setIcon(AllIcons.Actions.Lightning);
+ }
+
+ @Override
+ public void update(@NotNull AnActionEvent e) {
+ Presentation presentation = e.getPresentation();
+ final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
+ Location location = context.getLocation();
+ if (location == null) {
+ return;
+ }
+ PsiElement psiElement = location.getPsiElement();
+ PsiFile psiFile = psiElement.getContainingFile();
+ if (psiFile != null && psiFile.getName().contains(".answer")) {
+ presentation.setEnabled(true);
+ presentation.setText("Run tests from '" + psiFile.getName() + "'");
+ }
+ else {
+ presentation.setEnabled(false);
+ }
+ }
+
+ public void actionPerformed(@NotNull AnActionEvent e) {
+ final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
+ run(context);
+ }
+
+ public static void run(final @NotNull ConfigurationContext context) {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ final Project project = context.getProject();
+ PsiElement location = context.getPsiLocation();
+ final Course course = CCProjectService.getInstance(project).getCourse();
+ if (course == null || location == null) {
+ return;
+ }
+ PsiFile psiFile = location.getContainingFile();
+ final VirtualFile virtualFile = psiFile.getVirtualFile();
+ final VirtualFile taskDir = virtualFile.getParent();
+ if (taskDir == null) {
+ return;
+ }
+ final Task task = getTask(course, taskDir);
+ if (task == null) {
+ return;
+ }
+ for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+ final String name = entry.getKey();
+ createTestEnvironment(taskDir, name, entry.getValue(), project);
+ VirtualFile testFile = taskDir.findChild("tests.py");
+ if (testFile == null) {
+ return;
+ }
+ executeTests(project, virtualFile, taskDir, testFile);
+ clearTestEnvironment(taskDir, project);
+ }
+ }
+ });
+ }
+
+ private static void createTestEnvironment(@NotNull final VirtualFile taskDir, final String fileName, @NotNull final TaskFile taskFile,
+ @NotNull final Project project) {
+ try {
+ String answerFileName = FileUtil.getNameWithoutExtension(fileName) + ".answer";
+ final VirtualFile answerFile = taskDir.findChild(answerFileName);
+ if (answerFile == null) {
+ LOG.debug("could not find answer file " + answerFileName);
+ return;
+ }
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ final FileDocumentManager documentManager = FileDocumentManager.getInstance();
+ documentManager.saveAllDocuments();
+ }
+ });
+ answerFile.copy(project, taskDir, fileName);
+ flushWindows(taskFile, answerFile);
+ createResourceFiles(answerFile, project);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+
+ private static void clearTestEnvironment(@NotNull final VirtualFile taskDir, @NotNull final Project project) {
+ try {
+ VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
+ if (ideaDir == null) {
+ LOG.debug("idea directory doesn't exist");
+ return;
+ }
+ VirtualFile courseResourceDir = ideaDir.findChild("course");
+ if (courseResourceDir == null) {
+ return;
+ }
+ courseResourceDir.delete(project);
+ VirtualFile[] taskDirChildren = taskDir.getChildren();
+ for (VirtualFile file : taskDirChildren) {
+ if (file.getName().contains("_windows")) {
+ file.delete(project);
+ }
+ if (CCProjectService.getInstance(project).isTaskFile(file)) {
+ file.delete(project);
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+
+ private static void executeTests(@NotNull final Project project,
+ @NotNull final VirtualFile virtualFile,
+ @NotNull final VirtualFile taskDir,
+ @NotNull final VirtualFile testFile) {
+ final ConfigurationFactory factory = PythonConfigurationType.getInstance().getConfigurationFactories()[0];
+ final RunnerAndConfigurationSettings settings =
+ RunManager.getInstance(project).createRunConfiguration("test", factory);
+
+ final PythonRunConfiguration configuration = (PythonRunConfiguration)settings.getConfiguration();
+ configuration.setScriptName(testFile.getPath());
+ VirtualFile userFile = taskDir.findChild(virtualFile.getNameWithoutExtension() + ".py");
+ if (userFile == null) {
+ return;
+ }
+ VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
+ if (ideaDir == null) {
+ return;
+ }
+ VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
+ ProjectView.getInstance(project).refresh();
+ VirtualFile courseDir = ideaDir.findChild("course");
+ if (courseDir == null) {
+ return;
+ }
+ configuration.setScriptParameters(courseDir.getPath() + " " + userFile.getPath());
+ Executor executor = DefaultRunExecutor.getRunExecutorInstance();
+ ProgramRunnerUtil.executeConfiguration(project, settings, executor);
+ }
+
+ @Nullable
+ private static Task getTask(@NotNull final Course course, @NotNull final VirtualFile taskDir) {
+ if (!taskDir.getName().contains("task")) {
+ return null;
+ }
+ VirtualFile lessonDir = taskDir.getParent();
+ if (lessonDir == null || !lessonDir.getName().contains("lesson")) {
+ return null;
+ }
+ Lesson lesson = course.getLesson(lessonDir.getName());
+ if (lesson == null) {
+ return null;
+ }
+ return lesson.getTask(taskDir.getName());
+ }
+
+
+ //some tests could compare task files after user modifications with initial task files
+ private static void createResourceFiles(@NotNull final VirtualFile file, @NotNull final Project project) {
+ VirtualFile taskDir = file.getParent();
+ int index = CCProjectService.getIndex(taskDir.getName(), "task");
+ VirtualFile lessonDir = taskDir.getParent();
+ int lessonIndex = CCProjectService.getIndex(lessonDir.getName(), "lesson");
+ Course course = CCProjectService.getInstance(project).getCourse();
+ if (course == null) {
+ return;
+ }
+ VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
+ assert ideaDir != null;
+ try {
+ VirtualFile taskResourceDir = ideaDir.createChildDirectory(project, "course").createChildDirectory(project, lessonDir.getName())
+ .createChildDirectory(project, taskDir.getName());
+ if (CCProjectService.indexIsValid(lessonIndex, course.getLessons())) {
+ Lesson lesson = course.getLessons().get(lessonIndex);
+ if (CCProjectService.indexIsValid(index, lesson.getTaskList())) {
+ Task task = lesson.getTaskList().get(index);
+ HashMap<TaskFile, TaskFile> taskFilesCopy = new HashMap<TaskFile, TaskFile>();
+ for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+ CreateCourseArchive.createUserFile(project, taskFilesCopy, taskResourceDir, taskDir, entry);
+ CreateCourseArchive.resetTaskFiles(taskFilesCopy);
+ }
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+
+ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+ public static VirtualFile flushWindows(TaskFile taskFile, VirtualFile file) {
+ VirtualFile taskDir = file.getParent();
+ VirtualFile fileWindows = null;
+ final Document document = FileDocumentManager.getInstance().getDocument(file);
+ if (document == null) {
+ LOG.debug("Couldn't flush windows");
+ return null;
+ }
+ if (taskDir != null) {
+ String name = file.getNameWithoutExtension() + "_windows";
+ PrintWriter printWriter = null;
+ try {
+ fileWindows = taskDir.createChildData(taskFile, name);
+ printWriter = new PrintWriter(new FileOutputStream(fileWindows.getPath()));
+ for (TaskWindow taskWindow : taskFile.getTaskWindows()) {
+ int start = taskWindow.getRealStartOffset(document);
+ String windowDescription = document.getText(new TextRange(start, start + taskWindow.getReplacementLength()));
+ printWriter.println("#study_plugin_window = " + windowDescription);
+ }
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ FileDocumentManager.getInstance().saveDocument(document);
+ }
+ });
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ finally {
+ if (printWriter != null) {
+ printWriter.close();
+ }
+ }
+ }
+ return fileWindows;
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/RunTestsLineMarker.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/RunTestsLineMarker.java
new file mode 100644
index 000000000000..93e0a706f367
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/RunTestsLineMarker.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator;
+
+import com.intellij.codeHighlighting.Pass;
+import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
+import com.intellij.codeInsight.daemon.LineMarkerInfo;
+import com.intellij.codeInsight.daemon.LineMarkerProvider;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.DataManager;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.markup.GutterIconRenderer;
+import com.intellij.psi.PsiComment;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiWhiteSpace;
+import com.intellij.psi.util.PsiUtilBase;
+import com.intellij.util.Function;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyImportStatement;
+import com.jetbrains.python.psi.PyStatement;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.event.MouseEvent;
+import java.util.Collection;
+import java.util.List;
+
+public class RunTestsLineMarker implements LineMarkerProvider {
+ @Nullable
+ @Override
+ public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
+ return null;
+ }
+
+ @Override
+ public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
+ for (PsiElement element : elements) {
+ if (isFirstCodeLine(element)) {
+ PsiFile psiFile = element.getContainingFile();
+ if (psiFile == null || !psiFile.getName().contains(".answer")) {
+ continue;
+ }
+ result.add(new LineMarkerInfo<PsiElement>(
+ element, element.getTextRange(), AllIcons.Actions.Lightning, Pass.UPDATE_OVERRIDEN_MARKERS,
+ new Function<PsiElement, String>() {
+ @Override
+ public String fun(PsiElement e) {
+ return "Run tests from file '" + e.getContainingFile().getName() + "'";
+ }
+ },
+ new GutterIconNavigationHandler<PsiElement>() {
+ @Override
+ public void navigate(MouseEvent e, PsiElement elt) {
+ executeCurrentScript(elt);
+ }
+ },
+ GutterIconRenderer.Alignment.RIGHT));
+ }
+ }
+ }
+
+ private static void executeCurrentScript(PsiElement elt) {
+ Editor editor = PsiUtilBase.findEditor(elt);
+ assert editor != null;
+
+ final ConfigurationContext context =
+ ConfigurationContext.getFromContext(DataManager.getInstance().getDataContext(editor.getComponent()));
+ CCRunTests.run(context);
+ }
+
+ private static boolean isFirstCodeLine(PsiElement element) {
+ return element instanceof PyStatement &&
+ element.getParent() instanceof PyFile &&
+ !isNothing(element) &&
+ nothingBefore(element);
+ }
+
+ private static boolean nothingBefore(PsiElement element) {
+ element = element.getPrevSibling();
+ while (element != null) {
+ if (!isNothing(element)) {
+ return false;
+ }
+ element = element.getPrevSibling();
+ }
+
+ return true;
+ }
+
+ private static boolean isNothing(PsiElement element) {
+ return (element instanceof PsiComment) || (element instanceof PyImportStatement) || (element instanceof PsiWhiteSpace);
+ }
+}
+
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java
index d803e0e8fd97..965bd31771a0 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/StudyDocumentListener.java
@@ -44,9 +44,6 @@ public abstract class StudyDocumentListener extends DocumentAdapter {
@Override
public void documentChanged(DocumentEvent e) {
if (e instanceof DocumentEventImpl) {
- if (!needModify()) {
- return;
- }
DocumentEventImpl event = (DocumentEventImpl)e;
Document document = e.getDocument();
int offset = e.getOffset();
@@ -65,7 +62,5 @@ public abstract class StudyDocumentListener extends DocumentAdapter {
}
protected abstract void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change);
-
- protected abstract boolean needModify();
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java
index ff88cea5fd42..0bc631dbd784 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/AddTaskWindow.java
@@ -13,6 +13,7 @@ import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.coursecreator.CCProjectService;
import org.jetbrains.plugins.coursecreator.format.*;
import org.jetbrains.plugins.coursecreator.ui.CreateTaskWindowDialog;
@@ -59,12 +60,12 @@ public class AddTaskWindow extends DumbAwareAction {
}
int index = taskFile.getTaskWindows().size() + 1;
taskFile.addTaskWindow(taskWindow, index);
- taskWindow.drawHighlighter(editor);
+ taskWindow.drawHighlighter(editor, false);
DaemonCodeAnalyzerImpl.getInstance(project).restart(file);
}
@Override
- public void update(AnActionEvent event) {
+ public void update(@NotNull AnActionEvent event) {
final Presentation presentation = event.getPresentation();
final Project project = event.getData(CommonDataKeys.PROJECT);
if (project == null) {
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRename.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRename.java
new file mode 100644
index 000000000000..321a86a5ba35
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRename.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.ide.IdeView;
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.ide.util.DirectoryChooserUtil;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.Course;
+
+import javax.swing.*;
+
+public abstract class CCRename extends DumbAwareAction {
+ public CCRename(String text, String description, Icon icon) {
+ super(text, description, icon);
+ }
+
+ @Override
+ public void update(@NotNull AnActionEvent event) {
+ final Presentation presentation = event.getPresentation();
+ final Project project = event.getData(CommonDataKeys.PROJECT);
+ if (project == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+
+ final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
+ if (view == null) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+
+ final PsiDirectory[] directories = view.getDirectories();
+ if (directories.length == 0) {
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ return;
+ }
+ final PsiFile file = CommonDataKeys.PSI_FILE.getData(event.getDataContext());
+ final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
+ if (file != null ||directory == null || !directory.getName().contains(getFolderName())) {
+ presentation.setEnabled(false);
+ presentation.setVisible(false);
+ return;
+ }
+ presentation.setVisible(true);
+ presentation.setEnabled(true);
+ }
+
+ public abstract String getFolderName();
+
+ @Override
+ public void actionPerformed(@NotNull AnActionEvent e) {
+ final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
+ final Project project = e.getData(CommonDataKeys.PROJECT);
+
+ if (view == null || project == null) {
+ return;
+ }
+ final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
+ if (directory == null || !directory.getName().contains(getFolderName())) {
+ return;
+ }
+ Course course = CCProjectService.getInstance(project).getCourse();
+ if (course == null) {
+ return;
+ }
+ if (!processRename(project, directory, course)) return;
+ ProjectView.getInstance(project).refresh();
+ }
+
+ public abstract boolean processRename(Project project, PsiDirectory directory, Course course);
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameLesson.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameLesson.java
new file mode 100644
index 000000000000..3f580454ed6e
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameLesson.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.psi.PsiDirectory;
+import org.jetbrains.plugins.coursecreator.format.Course;
+import org.jetbrains.plugins.coursecreator.format.Lesson;
+
+public class CCRenameLesson extends CCRename {
+
+ public CCRenameLesson() {
+ super("Rename Lesson", "Rename Lesson", null);
+ }
+
+ @Override
+ public String getFolderName() {
+ return "lesson";
+ }
+
+ @Override
+ public boolean processRename(Project project, PsiDirectory directory, Course course) {
+ Lesson lesson = course.getLesson(directory.getName());
+ if (lesson == null) {
+ return false;
+ }
+ String newName = Messages.showInputDialog(project, "Enter new name", "Rename " + getFolderName(), null);
+ if (newName == null) {
+ return false;
+ }
+ lesson.setName(newName);
+ return true;
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameTask.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameTask.java
new file mode 100644
index 000000000000..342621bf0d47
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCRenameTask.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.psi.PsiDirectory;
+import org.jetbrains.plugins.coursecreator.format.Course;
+import org.jetbrains.plugins.coursecreator.format.Lesson;
+import org.jetbrains.plugins.coursecreator.format.Task;
+
+public class CCRenameTask extends CCRename {
+ public CCRenameTask() {
+ super("Rename Task", "Rename Task", null);
+ }
+
+ @Override
+ public String getFolderName() {
+ return "task";
+ }
+
+ @Override
+ public boolean processRename(Project project, PsiDirectory directory, Course course) {
+ PsiDirectory lessonDir = directory.getParent();
+ if (lessonDir == null || !lessonDir.getName().contains("lesson")) {
+ return false;
+ }
+ Lesson lesson = course.getLesson(lessonDir.getName());
+ if (lesson == null) {
+ return false;
+ }
+ Task task = lesson.getTask(directory.getName());
+ if (task == null) {
+ return false;
+ }
+ String newName = Messages.showInputDialog(project, "Enter new name", "Rename " + getFolderName(), null);
+ if (newName == null) {
+ return false;
+ }
+ task.setName(newName);
+ return true;
+
+ }
+}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCShowPreview.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCShowPreview.java
new file mode 100644
index 000000000000..bccaacaf1957
--- /dev/null
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CCShowPreview.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.plugins.coursecreator.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.containers.hash.HashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
+import org.jetbrains.plugins.coursecreator.format.*;
+
+import java.util.Map;
+
+public class CCShowPreview extends DumbAwareAction {
+ public CCShowPreview() {
+ super("Show preview","Show preview", null);
+ }
+
+ @Override
+ public void update(@NotNull AnActionEvent e) {
+ Presentation presentation = e.getPresentation();
+ presentation.setEnabled(false);
+ presentation.setVisible(false);
+ final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext());
+ if (file != null && file.getName().contains(".answer")) {
+ presentation.setEnabled(true);
+ presentation.setVisible(true);
+ }
+ }
+
+ @Override
+ public void actionPerformed(@NotNull AnActionEvent e) {
+ final Project project = e.getProject();
+ if (project == null) {
+ return;
+ }
+ final PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext());
+ if (file == null || !file.getName().contains(".answer")) {
+ return;
+ }
+ final PsiDirectory taskDir = file.getContainingDirectory();
+ if (taskDir == null) {
+ return;
+ }
+ PsiDirectory lessonDir = taskDir.getParentDirectory();
+ if (lessonDir == null) {
+ return;
+ }
+ Course course = CCProjectService.getInstance(project).getCourse();
+ if (course == null) {
+ return;
+ }
+ Lesson lesson = course.getLesson(lessonDir.getName());
+ Task task = lesson.getTask(taskDir.getName());
+ TaskFile taskFile = task.getTaskFile(file.getName());
+ final Map<TaskFile, TaskFile> taskFilesCopy = new HashMap<TaskFile, TaskFile>();
+ for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
+ if (entry.getValue() == taskFile) {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ CreateCourseArchive.createUserFile(project, taskFilesCopy, taskDir.getVirtualFile(), taskDir.getVirtualFile(), entry);
+ }
+ });
+ }
+ }
+ String userFileName = FileUtil.getNameWithoutExtension(file.getName()) + ".py";
+ VirtualFile userFile = taskDir.getVirtualFile().findChild(userFileName);
+ if (userFile != null) {
+ FileEditorManager.getInstance(project).openFile(userFile, true);
+ Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
+ if (editor == null) {
+ return;
+ }
+ for (TaskWindow taskWindow : taskFile.getTaskWindows()) {
+ taskWindow.drawHighlighter(editor, true);
+ }
+ CreateCourseArchive.resetTaskFiles(taskFilesCopy);
+ }
+ }
+} \ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java
index 05428f4e82d1..8db49156c987 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateCourseArchive.java
@@ -18,6 +18,7 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.util.io.ZipUtil;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.coursecreator.CCProjectService;
import org.jetbrains.plugins.coursecreator.StudyDocumentListener;
import org.jetbrains.plugins.coursecreator.format.*;
@@ -29,8 +30,8 @@ import java.util.zip.ZipOutputStream;
public class CreateCourseArchive extends DumbAwareAction {
private static final Logger LOG = Logger.getInstance(CreateCourseArchive.class.getName());
- String myZipName;
- String myLocationDir;
+ private String myZipName;
+ private String myLocationDir;
public void setZipName(String zipName) {
myZipName = zipName;
@@ -60,46 +61,124 @@ public class CreateCourseArchive extends DumbAwareAction {
}
final VirtualFile baseDir = project.getBaseDir();
final Map<String, Lesson> lessons = course.getLessonsMap();
- //List<FileEditor> editorList = new ArrayList<FileEditor>();
- Map<VirtualFile, TaskFile> taskFiles = new HashMap<VirtualFile, TaskFile>();
+ //map to store initial task file
+ final Map<TaskFile, TaskFile> taskFiles = new HashMap<TaskFile, TaskFile>();
for (Map.Entry<String, Lesson> lesson : lessons.entrySet()) {
final VirtualFile lessonDir = baseDir.findChild(lesson.getKey());
if (lessonDir == null) continue;
for (Map.Entry<String, Task> task : lesson.getValue().myTasksMap.entrySet()) {
final VirtualFile taskDir = lessonDir.findChild(task.getKey());
if (taskDir == null) continue;
- for (Map.Entry<String, TaskFile> entry : task.getValue().task_files.entrySet()) {
- final VirtualFile file = taskDir.findChild(entry.getKey());
- if (file == null) continue;
- final Document document = FileDocumentManager.getInstance().getDocument(file);
- if (document == null) continue;
- final TaskFile taskFile = entry.getValue();
- document.addDocumentListener(new InsertionListener(taskFile));
- taskFiles.put(file, taskFile);
- taskFile.setTrackChanges(false);
- Collections.sort(taskFile.getTaskWindows());
- for (int i = taskFile.getTaskWindows().size() - 1; i >=0 ; i--) {
- final TaskWindow taskWindow = taskFile.getTaskWindows().get(i);
- final String taskText = taskWindow.getTaskText();
- final int lineStartOffset = document.getLineStartOffset(taskWindow.line);
- final int offset = lineStartOffset + taskWindow.start;
- CommandProcessor.getInstance().executeCommand(project, new Runnable() {
- @Override
- public void run() {
- ApplicationManager.getApplication().runWriteAction(new Runnable() {
- @Override
- public void run() {
- document.replaceString(offset, offset + taskWindow.getReplacementLength(), taskText);
- FileDocumentManager.getInstance().saveDocument(document);
- }
- });
- }
- }, "x", "qwe");
- }
+ for (final Map.Entry<String, TaskFile> entry : task.getValue().task_files.entrySet()) {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ createUserFile(project, taskFiles, taskDir, taskDir, entry);
+ }
+ });
}
}
}
generateJson(project);
+ packCourse(baseDir, lessons);
+ resetTaskFiles(taskFiles);
+ synchronize(project);
+ }
+
+ public static void createUserFile(@NotNull final Project project,
+ @NotNull final Map<TaskFile, TaskFile> taskFilesCopy,
+ @NotNull final VirtualFile userFileDir,
+ @NotNull final VirtualFile answerFileDir,
+ @NotNull final Map.Entry<String, TaskFile> taskFiles) {
+ final String name = taskFiles.getKey();
+ VirtualFile file = userFileDir.findChild(name);
+ if (file != null) {
+ try {
+ file.delete(project);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ try {
+ userFileDir.createChildData(project, name);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+
+ file = userFileDir.findChild(name);
+ assert file != null;
+ String answerFileName = file.getNameWithoutExtension() + ".answer";
+ VirtualFile answerFile = answerFileDir.findChild(answerFileName);
+ if (answerFile == null) {
+ return;
+ }
+ final Document answerDocument = FileDocumentManager.getInstance().getDocument(answerFile);
+ if (answerDocument == null) {
+ return;
+ }
+ final Document document = FileDocumentManager.getInstance().getDocument(file);
+ if (document == null) return;
+ final TaskFile taskFile = taskFiles.getValue();
+ TaskFile taskFileSaved = new TaskFile();
+ taskFile.copy(taskFileSaved);
+ CommandProcessor.getInstance().executeCommand(project, new Runnable() {
+ @Override
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ document.replaceString(0, document.getTextLength(), answerDocument.getText());
+ }
+ });
+ }
+ }, "x", "qwe");
+ InsertionListener listener = new InsertionListener(taskFile);
+ document.addDocumentListener(listener);
+ taskFilesCopy.put(taskFile, taskFileSaved);
+ Collections.sort(taskFile.getTaskWindows());
+ for (int i = taskFile.getTaskWindows().size() - 1; i >= 0; i--) {
+ final TaskWindow taskWindow = taskFile.getTaskWindows().get(i);
+ replaceTaskWindow(project, document, taskWindow);
+ }
+ document.removeDocumentListener(listener);
+ }
+
+ private static void replaceTaskWindow(@NotNull final Project project,
+ @NotNull final Document document,
+ @NotNull final TaskWindow taskWindow) {
+ final String taskText = taskWindow.getTaskText();
+ final int lineStartOffset = document.getLineStartOffset(taskWindow.line);
+ final int offset = lineStartOffset + taskWindow.start;
+ CommandProcessor.getInstance().executeCommand(project, new Runnable() {
+ @Override
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ document.replaceString(offset, offset + taskWindow.getReplacementLength(), taskText);
+ FileDocumentManager.getInstance().saveDocument(document);
+ }
+ });
+ }
+ }, "x", "qwe");
+ }
+
+ private static void synchronize(@NotNull final Project project) {
+ VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
+ ProjectView.getInstance(project).refresh();
+ }
+
+ public static void resetTaskFiles(@NotNull final Map<TaskFile, TaskFile> taskFiles) {
+ for (Map.Entry<TaskFile, TaskFile> entry : taskFiles.entrySet()) {
+ TaskFile realTaskFile = entry.getKey();
+ TaskFile savedTaskFile = entry.getValue();
+ realTaskFile.update(savedTaskFile);
+ }
+ }
+
+ private void packCourse(@NotNull final VirtualFile baseDir, @NotNull final Map<String, Lesson> lessons) {
try {
File zipFile = new File(myLocationDir, myZipName + ".zip");
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
@@ -108,7 +187,12 @@ public class CreateCourseArchive extends DumbAwareAction {
final VirtualFile lessonDir = baseDir.findChild(entry.getKey());
if (lessonDir == null) continue;
- ZipUtil.addFileOrDirRecursively(zos, null, new File(lessonDir.getPath()), lessonDir.getName(), null, null);
+ ZipUtil.addFileOrDirRecursively(zos, null, new File(lessonDir.getPath()), lessonDir.getName(), new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return !pathname.getName().contains(".answer");
+ }
+ }, null);
}
ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "hints"), "hints", null, null);
ZipUtil.addFileOrDirRecursively(zos, null, new File(baseDir.getPath(), "course.json"), "course.json", null, null);
@@ -119,36 +203,9 @@ public class CreateCourseArchive extends DumbAwareAction {
catch (IOException e1) {
LOG.error(e1);
}
-
- for (Map.Entry<VirtualFile, TaskFile> entry: taskFiles.entrySet()) {
- TaskFile value = entry.getValue();
- final Document document = FileDocumentManager.getInstance().getDocument(entry.getKey());
- if (document == null) {
- continue;
- }
- for (final TaskWindow taskWindow : value.getTaskWindows()){
- final int lineStartOffset = document.getLineStartOffset(taskWindow.line);
- final int offset = lineStartOffset + taskWindow.start;
- CommandProcessor.getInstance().executeCommand(project, new Runnable() {
- @Override
- public void run() {
- ApplicationManager.getApplication().runWriteAction(new Runnable() {
- @Override
- public void run() {
- document.replaceString(offset, offset + taskWindow.length, taskWindow.getPossibleAnswer());
- FileDocumentManager.getInstance().saveDocument(document);
- }
- });
- }
- }, "x", "qwe");
- }
- value.setTrackChanges(true);
- }
- VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
- ProjectView.getInstance(project).refresh();
}
- private void generateJson(Project project) {
+ private static void generateJson(@NotNull final Project project) {
final CCProjectService service = CCProjectService.getInstance(project);
final Course course = service.getCourse();
final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();
@@ -179,7 +236,7 @@ public class CreateCourseArchive extends DumbAwareAction {
}
}
- private class InsertionListener extends StudyDocumentListener {
+ private static class InsertionListener extends StudyDocumentListener {
public InsertionListener(TaskFile taskFile) {
super(taskFile);
@@ -187,12 +244,7 @@ public class CreateCourseArchive extends DumbAwareAction {
@Override
protected void updateTaskWindowLength(CharSequence fragment, TaskWindow taskWindow, int change) {
- //we don't need to update task window length
- }
-
- @Override
- protected boolean needModify() {
- return true;
+ //we don't need to update task window length
}
}
} \ No newline at end of file
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java
index 0940135b97be..57a37b3f4194 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTask.java
@@ -18,6 +18,7 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.coursecreator.CCProjectService;
import org.jetbrains.plugins.coursecreator.format.Course;
import org.jetbrains.plugins.coursecreator.format.Lesson;
@@ -29,7 +30,7 @@ public class CreateTask extends DumbAwareAction {
}
@Override
- public void actionPerformed(AnActionEvent e) {
+ public void actionPerformed(final AnActionEvent e) {
final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
final Project project = e.getData(CommonDataKeys.PROJECT);
@@ -42,7 +43,7 @@ public class CreateTask extends DumbAwareAction {
final CCProjectService service = CCProjectService.getInstance(project);
final Course course = service.getCourse();
final Lesson lesson = course.getLesson(directory.getName());
- final int size = lesson.getTasklist().size();
+ final int size = lesson.getTaskList().size();
final String taskName = Messages.showInputDialog("Name:", "Task Name", null, "task" + (size + 1), null);
if (taskName == null) return;
@@ -54,17 +55,16 @@ public class CreateTask extends DumbAwareAction {
if (taskDirectory != null) {
final FileTemplate template = FileTemplateManager.getInstance().getInternalTemplate("task.html");
final FileTemplate testsTemplate = FileTemplateManager.getInstance().getInternalTemplate("tests");
- final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.py");
+ final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.answer");
try {
final PsiElement taskFile = FileTemplateUtil.createFromTemplate(template, "task.html", null, taskDirectory);
final PsiElement testsFile = FileTemplateUtil.createFromTemplate(testsTemplate, "tests.py", null, taskDirectory);
- final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, "file1" + ".py", null, taskDirectory);
+ final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, "file1", null, taskDirectory);
final Task task = new Task(taskName);
- task.addTaskFile(taskPyFile.getContainingFile().getName(), size + 1);
+ task.addTaskFile("file1.py", size + 1);
task.setIndex(size + 1);
lesson.addTask(task, taskDirectory);
-
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
@@ -84,7 +84,7 @@ public class CreateTask extends DumbAwareAction {
}
@Override
- public void update(AnActionEvent event) {
+ public void update(@NotNull AnActionEvent event) {
final Presentation presentation = event.getPresentation();
final Project project = event.getData(CommonDataKeys.PROJECT);
if (project == null) {
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java
index 5aafcebefd6e..368be275a5a4 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/actions/CreateTaskFile.java
@@ -55,10 +55,10 @@ public class CreateTaskFile extends DumbAwareAction {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
- final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.py");
+ final FileTemplate taskTemplate = FileTemplateManager.getInstance().getInternalTemplate("task.answer");
try {
- final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, taskFileName + ".py", null, taskDir);
- task.addTaskFile(taskPyFile.getContainingFile().getName(), index);
+ final PsiElement taskPyFile = FileTemplateUtil.createFromTemplate(taskTemplate, taskFileName, null, taskDir);
+ task.addTaskFile(taskFileName + ".py", index);
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java
index eb62d59cd9b1..e124a6eb305d 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Course.java
@@ -4,10 +4,7 @@ import com.google.gson.annotations.Expose;
import com.intellij.psi.PsiDirectory;
import org.jetbrains.annotations.NotNull;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
public class Course {
@Expose public List<Lesson> lessons = new ArrayList<Lesson>();
@@ -52,4 +49,13 @@ public class Course {
public String getDescription() {
return description;
}
+
+ public void init() {
+ lessons.clear();
+ for (Lesson lesson: myLessonsMap.values()) {
+ lessons.add(lesson);
+ lesson.init();
+ }
+ Collections.sort(lessons);
+ }
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java
index 38720140caf1..bd91e8ec30af 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Lesson.java
@@ -4,12 +4,9 @@ import com.google.gson.annotations.Expose;
import com.intellij.psi.PsiDirectory;
import org.jetbrains.annotations.NotNull;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
-public class Lesson {
+public class Lesson implements Comparable{
@Expose public String name;
@Expose public List<Task> task_list = new ArrayList<Task>();
@@ -27,11 +24,15 @@ public class Lesson {
task_list.add(task);
}
+ public void setName(String name) {
+ this.name = name;
+ }
+
public Task getTask(@NotNull final String name) {
return myTasksMap.get(name);
}
- public List<Task> getTasklist() {
+ public List<Task> getTaskList() {
return task_list;
}
@@ -42,4 +43,22 @@ public class Lesson {
public int getIndex() {
return myIndex;
}
+
+ public Map<String, Task> getTasksMap() {
+ return myTasksMap;
+ }
+
+ public void init() {
+ task_list.clear();
+ for (Task task : myTasksMap.values()) {
+ task_list.add(task);
+ }
+ Collections.sort(task_list);
+ }
+
+ @Override
+ public int compareTo(@NotNull Object o) {
+ Lesson lesson = (Lesson) o;
+ return myIndex - lesson.getIndex();
+ }
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java
index e6c085b5d6a1..886add86ceb4 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/Task.java
@@ -2,11 +2,12 @@ package org.jetbrains.plugins.coursecreator.format;
import com.google.gson.annotations.Expose;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.coursecreator.CCProjectService;
import java.util.HashMap;
import java.util.Map;
-public class Task {
+public class Task implements Comparable{
@Expose public String name;
@Expose public Map<String, TaskFile> task_files = new HashMap<String, TaskFile>();
public int myIndex;
@@ -28,7 +29,8 @@ public class Task {
}
public TaskFile getTaskFile(@NotNull final String name) {
- return task_files.get(name);
+ String fileName = CCProjectService.getRealTaskFileName(name);
+ return fileName != null ? task_files.get(fileName) : null;
}
public void setIndex(int index) {
@@ -38,4 +40,18 @@ public class Task {
public Map<String, TaskFile> getTaskFiles() {
return task_files;
}
+
+ public boolean isTaskFile(String name) {
+ return task_files.get(name) != null;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public int compareTo(@NotNull Object o) {
+ Task task = (Task) o;
+ return myIndex - task.getIndex();
+ }
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java
index 85f0d91983f2..b88e375bc265 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskFile.java
@@ -13,15 +13,6 @@ import java.util.List;
public class TaskFile {
@Expose public List<TaskWindow> task_windows = new ArrayList<TaskWindow>();
public int myIndex;
- public boolean myTrackChanges = true;
-
- public boolean isTrackChanges() {
- return myTrackChanges;
- }
-
- public void setTrackChanges(boolean trackChanges) {
- myTrackChanges = trackChanges;
- }
public TaskFile() {}
@@ -108,4 +99,22 @@ public class TaskFile {
}
}
}
+
+ public void copy(@NotNull final TaskFile target) {
+ target.setIndex(myIndex);
+ for (TaskWindow taskWindow : task_windows) {
+ TaskWindow savedWindow = new TaskWindow(taskWindow.getLine(), taskWindow.getStart(),
+ taskWindow.getLength(), "");
+ target.getTaskWindows().add(savedWindow);
+ savedWindow.setIndex(taskWindow.getIndex());
+ }
+ }
+
+ public void update(@NotNull final TaskFile source) {
+ for (TaskWindow taskWindow : source.getTaskWindows()) {
+ TaskWindow taskWindowUpdated = task_windows.get(taskWindow.getIndex() - 1);
+ taskWindowUpdated.setLine(taskWindow.getLine());
+ taskWindowUpdated.setStart(taskWindow.getStart());
+ }
+ }
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java
index cb6418ec75d7..6b1be7ef3e7d 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/format/TaskWindow.java
@@ -68,16 +68,17 @@ public class TaskWindow implements Comparable{
}
}
- public void drawHighlighter(@NotNull final Editor editor) {
+ public void drawHighlighter(@NotNull final Editor editor, boolean useLength) {
int startOffset = editor.getDocument().getLineStartOffset(line) + start;
- int endOffset = startOffset + myReplacementLength;
+ int highlighterLength = useLength ? length : myReplacementLength;
+ int endOffset = startOffset + highlighterLength;
TextAttributes defaultTestAttributes =
EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.LIVE_TEMPLATE_ATTRIBUTES);
RangeHighlighter highlighter =
editor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, HighlighterLayer.LAST + 1, defaultTestAttributes,
HighlighterTargetArea.EXACT_RANGE);
highlighter.setGreedyToLeft(true);
- highlighter.setGreedyToRight(true);
+ highlighter.setGreedyToRight(false);
}
public int getIndex() {
@@ -123,10 +124,6 @@ public class TaskWindow implements Comparable{
return lineDiff;
}
- public String getPossibleAnswer() {
- return possible_answer;
- }
-
public int getLength() {
return length;
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java
index 1a7304123f0a..0969c54563c2 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCDirectoryNode.java
@@ -26,6 +26,8 @@ public class CCDirectoryNode extends PsiDirectoryNode {
@Override
protected void updateImpl(PresentationData data) {
+ //TODO:change presentable name for files with suffix _answer
+
String valueName = myValue.getName();
final Course course = CCProjectService.getInstance(myProject).getCourse();
if (course == null) return;
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java
index 69b78ec9a92b..b4fb50a96dc4 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/projectView/CCTreeStructureProvider.java
@@ -2,9 +2,11 @@ package org.jetbrains.plugins.coursecreator.projectView;
import com.intellij.ide.projectView.TreeStructureProvider;
import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -27,11 +29,21 @@ public class CCTreeStructureProvider implements TreeStructureProvider, DumbAware
Project project = node.getProject();
if (project != null) {
if (node.getValue() instanceof PsiDirectory) {
- PsiDirectory directory = (PsiDirectory) node.getValue();
+ PsiDirectory directory = (PsiDirectory)node.getValue();
nodes.add(new CCDirectoryNode(project, directory, settings));
- } else {
- nodes.add(node);
+ continue;
}
+ if (node instanceof PsiFileNode) {
+ PsiFileNode fileNode = (PsiFileNode)node;
+ VirtualFile virtualFile = fileNode.getVirtualFile();
+ if (virtualFile == null) {
+ continue;
+ }
+ if (CCProjectService.getInstance(project).isTaskFile(virtualFile)) {
+ continue;
+ }
+ }
+ nodes.add(node);
}
}
return nodes;
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form
index 920dcb9494a7..096a85f8da59 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateCourseArchivePanel.form
@@ -27,7 +27,7 @@
<component id="160bb" class="javax.swing.JTextField" binding="myNameField" default-binding="true">
<constraints>
<grid row="0" column="1" row-span="2" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
- <preferred-size width="150" height="-1"/>
+ <preferred-size width="300" height="-1"/>
</grid>
</constraints>
<properties/>
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java
index c7e8f715672c..53a4a77b97f7 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowDialog.java
@@ -150,4 +150,10 @@ public class CreateTaskWindowDialog extends DialogWrapper {
public void validateInput() {
super.initValidation();
}
+
+ @Nullable
+ @Override
+ public JComponent getPreferredFocusedComponent() {
+ return myPanel.getPreferredFocusedComponent();
+ }
}
diff --git a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java
index 21a7eb063c63..a50840deaf2f 100644
--- a/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java
+++ b/python/edu/course-creator/src/org/jetbrains/plugins/coursecreator/ui/CreateTaskWindowPanel.java
@@ -37,6 +37,7 @@ public class CreateTaskWindowPanel extends JPanel {
}
});
+ myTaskWindowText.grabFocus();
myHintName.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
@@ -93,4 +94,8 @@ public class CreateTaskWindowPanel extends JPanel {
public void setGeneratedHintName(String generatedHintName) {
myGeneratedHintName = generatedHintName;
}
+
+ public JComponent getPreferredFocusedComponent() {
+ return myTaskWindowText;
+ }
}
diff --git a/python/edu/learn-python/resources/META-INF/plugin.xml b/python/edu/learn-python/resources/META-INF/plugin.xml
index 8e8bddcce5e5..b6ff0ce5d0d7 100644
--- a/python/edu/learn-python/resources/META-INF/plugin.xml
+++ b/python/edu/learn-python/resources/META-INF/plugin.xml
@@ -40,7 +40,7 @@
<actions>
<action id="CheckAction" class="com.jetbrains.python.edu.actions.StudyCheckAction" text="check"
- description="Runs tests for current tasks" icon="/icons/icon.jpg">
+ description="Runs tests for current tasks">
</action>
<action id="PrevWindowAction" class="com.jetbrains.python.edu.actions.StudyPrevWindowAction" text="PrevWindowAction" description="prev">
</action>
@@ -60,10 +60,12 @@
<add-to-group group-id="MainToolBar" anchor="last"/>
</action>
- <action id="WelcomeScreen.LearnPython" class="com.jetbrains.python.edu.actions.StudyNewProject" icon="StudyIcons.EducationalProjectType">
+ <action id="WelcomeScreen.PythonIntro" class="com.jetbrains.python.edu.actions.StudyIntroductionCourseAction" icon="StudyIcons.EducationalProjectType">
<add-to-group group-id="WelcomeScreen.QuickStart" anchor="first"/>
</action>
+ <action id="ReloadCourseAction" class="com.jetbrains.python.edu.actions.StudyReloadCourseAction"/>
+
</actions>
<extensions defaultExtensionNs="com.intellij">
@@ -74,6 +76,7 @@
<highlightErrorFilter implementation="com.jetbrains.python.edu.StudyHighlightErrorFilter"/>
<applicationService serviceInterface="com.intellij.openapi.fileEditor.impl.EditorEmptyTextPainter"
serviceImplementation="com.jetbrains.python.edu.StudyInstructionPainter" overrides="true"/>
+ <errorHandler implementation="com.intellij.diagnostic.ITNReporter"/>
</extensions>
<extensions defaultExtensionNs="Pythonid">
<visitorFilter language="Python" implementationClass="com.jetbrains.python.edu.highlighting.StudyVisitorFilter"/>
diff --git a/python/edu/learn-python/resources/courses/introduction_course.zip b/python/edu/learn-python/resources/courses/introduction_course.zip
index c39695e0c380..4fd2dab43830 100644
--- a/python/edu/learn-python/resources/courses/introduction_course.zip
+++ b/python/edu/learn-python/resources/courses/introduction_course.zip
Binary files differ
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java
index 59bd8bc2ea7c..b558667734bc 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyDirectoryProjectGenerator.java
@@ -53,7 +53,7 @@ public class StudyDirectoryProjectGenerator extends PythonProjectGenerator imple
@NotNull
@Override
public String getName() {
- return "Learn Python";
+ return "Educational";
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java
index b0cd5ba89fed..1800deb0a0d9 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/StudyTestRunner.java
@@ -61,7 +61,9 @@ public class StudyTestRunner {
try {
while ((line = testOutputReader.readLine()) != null) {
if (line.contains(TEST_FAILED)) {
- return line.substring(TEST_FAILED.length(), line.length());
+ String res = line.substring(TEST_FAILED.length(), line.length());
+ StudyUtils.closeSilently(testOutputReader);
+ return res;
}
}
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyIntroductionCourseAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyIntroductionCourseAction.java
new file mode 100644
index 000000000000..ed1fff22878f
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyIntroductionCourseAction.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2013 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.ide.impl.ProjectUtil;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.jetbrains.python.configuration.PyConfigurableInterpreterList;
+import com.jetbrains.python.edu.StudyDirectoryProjectGenerator;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.CourseInfo;
+import com.jetbrains.python.newProject.actions.GenerateProjectCallback;
+import com.jetbrains.python.newProject.actions.ProjectSpecificSettingsStep;
+import icons.StudyIcons;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+public class StudyIntroductionCourseAction extends AnAction {
+
+ public StudyIntroductionCourseAction() {
+ super("Introduction to Python", "Introduction to Python", StudyIcons.EducationalProjectType);
+ }
+
+ @Override
+ public void actionPerformed(@NotNull AnActionEvent e) {
+ final File projectDir = new File(ProjectUtil.getBaseDir(), "PythonIntroduction");
+ if (projectDir.exists()) {
+ ProjectUtil.openProject(projectDir.getPath(), null, false);
+ }
+ else {
+ final GenerateProjectCallback callback = new GenerateProjectCallback(null);
+ final StudyDirectoryProjectGenerator generator = new StudyDirectoryProjectGenerator();
+ final Map<CourseInfo, File> courses = generator.getCourses();
+ CourseInfo introCourse = null;
+ for (CourseInfo info : courses.keySet()) {
+ if ("Introduction to Python".equals(info.getName())) {
+ introCourse = info;
+ }
+ }
+ if (introCourse == null) {
+ introCourse = StudyUtils.getFirst(courses.keySet());
+ }
+ generator.setSelectedCourse(introCourse);
+ final ProjectSpecificSettingsStep step = new ProjectSpecificSettingsStep(generator, callback, true);
+
+ step.createPanel(); // initialize panel to set location
+ step.setLocation(projectDir.toString());
+
+ final Project project = ProjectManager.getInstance().getDefaultProject();
+ final List<Sdk> sdks = PyConfigurableInterpreterList.getInstance(project).getAllPythonSdks();
+ Sdk sdk = sdks.isEmpty() ? null : sdks.iterator().next();
+ step.setSdk(sdk);
+ callback.consume(step);
+ }
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java
deleted file mode 100644
index 0b75c4bc01c4..000000000000
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyNewProject.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2000-2013 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.edu.actions;
-
-import com.jetbrains.python.edu.StudyDirectoryProjectGenerator;
-import com.jetbrains.python.newProject.actions.GenerateProjectCallback;
-import com.jetbrains.python.newProject.actions.ProjectSpecificAction;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-public class StudyNewProject extends ProjectSpecificAction {
-
- public StudyNewProject(@NotNull final String name, @Nullable final Runnable runnable) {
- super(new GenerateProjectCallback(runnable), new StudyDirectoryProjectGenerator(), name, true);
- }
-
- public StudyNewProject() {
- this("Learn Python", null);
- }
-
-}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java
index a9448ddea0e3..a6c16d2553bf 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyRefreshTaskFileAction.java
@@ -22,108 +22,139 @@ import com.jetbrains.python.edu.StudyTaskManager;
import com.jetbrains.python.edu.StudyUtils;
import com.jetbrains.python.edu.course.*;
import com.jetbrains.python.edu.editor.StudyEditor;
+import org.jetbrains.annotations.NotNull;
import java.io.*;
public class StudyRefreshTaskFileAction extends DumbAwareAction {
private static final Logger LOG = Logger.getInstance(StudyRefreshTaskFileAction.class.getName());
- public void refresh(final Project project) {
- ApplicationManager.getApplication().invokeLater(new Runnable() {
+ public static void refresh(final Project project) {
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
@Override
public void run() {
- ApplicationManager.getApplication().runWriteAction(new Runnable() {
- @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
- @Override
- public void run() {
- final Editor editor = StudyEditor.getSelectedEditor(project);
- assert editor != null;
- final Document document = editor.getDocument();
- StudyDocumentListener listener = StudyEditor.getListener(document);
- if (listener != null) {
- document.removeDocumentListener(listener);
- }
- final int lineCount = document.getLineCount();
- if (lineCount != 0) {
- CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
- @Override
- public void run() {
- document.deleteString(0, document.getLineEndOffset(lineCount - 1));
- }
- });
- }
- StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
- Course course = taskManager.getCourse();
- assert course != null;
- File resourceFile = new File(course.getResourcePath());
- File resourceRoot = resourceFile.getParentFile();
- FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
- VirtualFile openedFile = fileDocumentManager.getFile(document);
- assert openedFile != null;
- final TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
- assert selectedTaskFile != null;
- Task currentTask = selectedTaskFile.getTask();
- String lessonDir = Lesson.LESSON_DIR + String.valueOf(currentTask.getLesson().getIndex() + 1);
- String taskDir = Task.TASK_DIR + String.valueOf(currentTask.getIndex() + 1);
- File pattern = new File(new File(new File(resourceRoot, lessonDir), taskDir), openedFile.getName());
- BufferedReader reader = null;
- try {
- reader = new BufferedReader(new InputStreamReader(new FileInputStream(pattern)));
- String line;
- StringBuilder patternText = new StringBuilder();
- while ((line = reader.readLine()) != null) {
- patternText.append(line);
- patternText.append("\n");
- }
- int patternLength = patternText.length();
- if (patternText.charAt(patternLength - 1) == '\n') {
- patternText.delete(patternLength - 1, patternLength);
- }
- document.setText(patternText);
- StudyStatus oldStatus = currentTask.getStatus();
- LessonInfo lessonInfo = currentTask.getLesson().getLessonInfo();
- lessonInfo.update(oldStatus, -1);
- lessonInfo.update(StudyStatus.Unchecked, +1);
- StudyUtils.updateStudyToolWindow(project);
- for (TaskWindow taskWindow : selectedTaskFile.getTaskWindows()) {
- taskWindow.reset();
- }
- ProjectView.getInstance(project).refresh();
- if (listener != null) {
- document.addDocumentListener(listener);
- }
- selectedTaskFile.drawAllWindows(editor);
- ApplicationManager.getApplication().invokeLater(new Runnable() {
- @Override
- public void run() {
- IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true);
- }
- });
- selectedTaskFile.navigateToFirstTaskWindow(editor);
- BalloonBuilder balloonBuilder =
- JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can now start again", MessageType.INFO, null);
- final Balloon balloon = balloonBuilder.createBalloon();
- StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
- assert selectedStudyEditor != null;
- balloon.showInCenterOf(selectedStudyEditor.getRefreshButton());
- Disposer.register(project, balloon);
- }
- catch (FileNotFoundException e1) {
- LOG.error(e1);
- }
- catch (IOException e1) {
- LOG.error(e1);
- }
- finally {
- StudyUtils.closeSilently(reader);
- }
- }
- });
+ final Editor editor = StudyEditor.getSelectedEditor(project);
+ assert editor != null;
+ final Document document = editor.getDocument();
+ refreshFile(editor, document, project);
}
});
+ }
+ });
}
- public void actionPerformed(AnActionEvent e) {
+ public static void refreshFile(@NotNull final Editor editor, @NotNull final Document document, @NotNull final Project project) {
+ StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
+ Course course = taskManager.getCourse();
+ assert course != null;
+ FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
+ VirtualFile openedFile = fileDocumentManager.getFile(document);
+ assert openedFile != null;
+ final TaskFile selectedTaskFile = taskManager.getTaskFile(openedFile);
+ assert selectedTaskFile != null;
+ String openedFileName = openedFile.getName();
+ Task currentTask = selectedTaskFile.getTask();
+ resetTaskFile(document, project, course, selectedTaskFile, openedFileName, currentTask);
+ selectedTaskFile.drawAllWindows(editor);
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ IdeFocusManager.getInstance(project).requestFocus(editor.getContentComponent(), true);
+ }
+ });
+ selectedTaskFile.navigateToFirstTaskWindow(editor);
+ showBaloon(project);
+ }
+
+ public static void resetTaskFile(Document document, Project project, Course course, TaskFile taskFile, String name, Task task) {
+ resetDocument(document, course, name, task);
+ updateLessonInfo(task);
+ StudyUtils.updateStudyToolWindow(project);
+ resetTaskWindows(taskFile);
+ ProjectView.getInstance(project).refresh();
+ }
+
+ private static void showBaloon(Project project) {
+ BalloonBuilder balloonBuilder =
+ JBPopupFactory.getInstance().createHtmlTextBalloonBuilder("You can now start again", MessageType.INFO, null);
+ final Balloon balloon = balloonBuilder.createBalloon();
+ StudyEditor selectedStudyEditor = StudyEditor.getSelectedStudyEditor(project);
+ assert selectedStudyEditor != null;
+ balloon.showInCenterOf(selectedStudyEditor.getRefreshButton());
+ Disposer.register(project, balloon);
+ }
+
+ private static void resetTaskWindows(TaskFile selectedTaskFile) {
+ for (TaskWindow taskWindow : selectedTaskFile.getTaskWindows()) {
+ taskWindow.reset();
+ }
+ }
+
+ private static void updateLessonInfo(Task currentTask) {
+ StudyStatus oldStatus = currentTask.getStatus();
+ LessonInfo lessonInfo = currentTask.getLesson().getLessonInfo();
+ lessonInfo.update(oldStatus, -1);
+ lessonInfo.update(StudyStatus.Unchecked, +1);
+ }
+
+ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
+ private static void resetDocument(Document document, Course course, String fileName, Task task) {
+ BufferedReader reader = null;
+ StudyDocumentListener listener = StudyEditor.getListener(document);
+ if (listener != null) {
+ document.removeDocumentListener(listener);
+ }
+ clearDocument(document);
+ try {
+ String lessonDir = Lesson.LESSON_DIR + String.valueOf(task.getLesson().getIndex() + 1);
+ String taskDir = Task.TASK_DIR + String.valueOf(task.getIndex() + 1);
+ File resourceFile = new File(course.getResourcePath());
+ File resourceRoot = resourceFile.getParentFile();
+ File pattern = new File(new File(new File(resourceRoot, lessonDir), taskDir), fileName);
+ reader = new BufferedReader(new InputStreamReader(new FileInputStream(pattern)));
+ String line;
+ StringBuilder patternText = new StringBuilder();
+ while ((line = reader.readLine()) != null) {
+ patternText.append(line);
+ patternText.append("\n");
+ }
+ int patternLength = patternText.length();
+ if (patternText.charAt(patternLength - 1) == '\n') {
+ patternText.delete(patternLength - 1, patternLength);
+ }
+ document.setText(patternText);
+ }
+ catch (FileNotFoundException e) {
+ LOG.error(e);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ finally {
+ StudyUtils.closeSilently(reader);
+ }
+ if (listener != null) {
+ document.addDocumentListener(listener);
+ }
+ }
+
+ private static void clearDocument(final Document document) {
+ final int lineCount = document.getLineCount();
+ if (lineCount != 0) {
+ CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
+ @Override
+ public void run() {
+ document.deleteString(0, document.getLineEndOffset(lineCount - 1));
+ }
+ });
+ }
+ }
+
+ public void actionPerformed(@NotNull AnActionEvent e) {
refresh(e.getProject());
}
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyReloadCourseAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyReloadCourseAction.java
new file mode 100644
index 000000000000..beefaaa94f7e
--- /dev/null
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyReloadCourseAction.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.edu.actions;
+
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ui.tree.TreeUtil;
+import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.StudyUtils;
+import com.jetbrains.python.edu.course.Course;
+import com.jetbrains.python.edu.course.Lesson;
+import com.jetbrains.python.edu.course.Task;
+import com.jetbrains.python.edu.course.TaskFile;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import javax.swing.tree.TreePath;
+import java.util.List;
+import java.util.Map;
+
+public class StudyReloadCourseAction extends DumbAwareAction {
+
+ public StudyReloadCourseAction() {
+ super("Reload Course", "Reload Course", null);
+ }
+
+ @Override
+ public void update(@NotNull AnActionEvent e) {
+ Presentation presentation = e.getPresentation();
+ Project project = e.getProject();
+ if (project != null) {
+ Course course = StudyTaskManager.getInstance(project).getCourse();
+ if (course != null) {
+ presentation.setVisible(true);
+ presentation.setEnabled(true);
+ }
+ }
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ }
+
+ @Override
+ public void actionPerformed(@NotNull AnActionEvent e) {
+ Project project = e.getProject();
+ if (project == null) {
+ return;
+ }
+ reloadCourse(project);
+ }
+
+ public static void reloadCourse(@NotNull final Project project) {
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ @Override
+ public void run() {
+ Course course = StudyTaskManager.getInstance(project).getCourse();
+ if (course == null) {
+ return;
+ }
+ for (VirtualFile file : FileEditorManager.getInstance(project).getOpenFiles()) {
+ FileEditorManager.getInstance(project).closeFile(file);
+ }
+ JTree tree = ProjectView.getInstance(project).getCurrentProjectViewPane().getTree();
+ TreePath path = TreeUtil.getFirstNodePath(tree);
+ tree.collapsePath(path);
+ List<Lesson> lessons = course.getLessons();
+ for (Lesson lesson : lessons) {
+ List<Task> tasks = lesson.getTaskList();
+ VirtualFile lessonDir = project.getBaseDir().findChild(Lesson.LESSON_DIR + (lesson.getIndex() + 1));
+ if (lessonDir == null) {
+ continue;
+ }
+ for (Task task : tasks) {
+ VirtualFile taskDir = lessonDir.findChild(Task.TASK_DIR + (task.getIndex() + 1));
+ if (taskDir == null) {
+ continue;
+ }
+ Map<String, TaskFile> taskFiles = task.getTaskFiles();
+ for (Map.Entry<String, TaskFile> entry : taskFiles.entrySet()) {
+ String name = entry.getKey();
+ TaskFile taskFile = entry.getValue();
+ VirtualFile file = taskDir.findChild(name);
+ if (file == null) {
+ continue;
+ }
+ Document document = FileDocumentManager.getInstance().getDocument(file);
+ if (document == null) {
+ continue;
+ }
+ StudyRefreshTaskFileAction.resetTaskFile(document, project, course, taskFile, name, task);
+ }
+ }
+ }
+ Lesson firstLesson = StudyUtils.getFirst(lessons);
+ if (firstLesson == null) {
+ return;
+ }
+ Task firstTask = StudyUtils.getFirst(firstLesson.getTaskList());
+ VirtualFile lessonDir = project.getBaseDir().findChild(Lesson.LESSON_DIR + (firstLesson.getIndex() + 1));
+ if (lessonDir != null) {
+ VirtualFile taskDir = lessonDir.findChild(Task.TASK_DIR + (firstTask.getIndex() + 1));
+ if (taskDir != null) {
+ ProjectView.getInstance(project).select(taskDir, taskDir, true);
+ }
+ }
+ }
+ });
+ }
+ });
+ }
+}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
index 46c0981cb964..b98bbd098776 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/actions/StudyTaskNavigationAction.java
@@ -1,5 +1,6 @@
package com.jetbrains.python.edu.actions;
+import com.intellij.ide.projectView.ProjectView;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.DumbAwareAction;
@@ -12,6 +13,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
+import com.intellij.util.ui.tree.TreeUtil;
import com.jetbrains.python.edu.StudyState;
import com.jetbrains.python.edu.course.Lesson;
import com.jetbrains.python.edu.course.Task;
@@ -20,6 +22,7 @@ import com.jetbrains.python.edu.editor.StudyEditor;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
+import javax.swing.tree.TreePath;
import java.util.Map;
@@ -74,7 +77,11 @@ abstract public class StudyTaskNavigationAction extends DumbAwareAction {
}
}
}
+ JTree tree = ProjectView.getInstance(project).getCurrentProjectViewPane().getTree();
+ TreePath path = TreeUtil.getFirstNodePath(tree);
+ tree.collapsePath(path);
if (shouldBeActive != null) {
+ ProjectView.getInstance(project).select(shouldBeActive, shouldBeActive, false);
FileEditorManager.getInstance(project).openFile(shouldBeActive, true);
}
ToolWindow runToolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.RUN);
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java
index 2f80dba12695..d8faacd23946 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/projectView/StudyDirectoryNode.java
@@ -1,8 +1,10 @@
package com.jetbrains.python.edu.projectView;
import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ProjectView;
import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
+import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
@@ -17,6 +19,7 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
+import java.util.Set;
public class StudyDirectoryNode extends PsiDirectoryNode {
private final PsiDirectory myValue;
@@ -110,4 +113,56 @@ public class StudyDirectoryNode extends PsiDirectoryNode {
data.addText(additionalName, new SimpleTextAttributes(Font.PLAIN, color));
data.setIcon(icon);
}
+
+ @Override
+ public boolean canNavigate() {
+ return true;
+ }
+
+ @Override
+ public boolean canNavigateToSource() {
+ return true;
+ }
+
+ @Override
+ public void navigate(boolean requestFocus) {
+ if (myValue.getName().contains(Task.TASK_DIR)) {
+ TaskFile taskFile = null;
+ VirtualFile virtualFile = null;
+ for (PsiElement child : myValue.getChildren()) {
+ VirtualFile childFile = child.getContainingFile().getVirtualFile();
+ taskFile = StudyTaskManager.getInstance(myProject).getTaskFile(childFile);
+ if (taskFile != null) {
+ virtualFile = childFile;
+ break;
+ }
+ }
+ if (taskFile != null) {
+ VirtualFile taskDir = virtualFile.getParent();
+ Task task = taskFile.getTask();
+ for (VirtualFile openFile : FileEditorManager.getInstance(myProject).getOpenFiles()) {
+ FileEditorManager.getInstance(myProject).closeFile(openFile);
+ }
+ VirtualFile child = null;
+ Set<String> fileNames = task.getTaskFiles().keySet();
+ for (String name : fileNames) {
+ child = taskDir.findChild(name);
+ if (child != null) {
+ FileEditorManager.getInstance(myProject).openFile(child, true);
+ }
+ }
+ if (child != null) {
+ ProjectView.getInstance(myProject).select(child, child, false);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean expandOnDoubleClick() {
+ if (myValue.getName().contains(Task.TASK_DIR)) {
+ return false;
+ }
+ return super.expandOnDoubleClick();
+ }
}
diff --git a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java
index a553978c416a..0f8c5a53511a 100644
--- a/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java
+++ b/python/edu/learn-python/src/com/jetbrains/python/edu/ui/StudyToolWindowFactory.java
@@ -9,6 +9,7 @@ import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.python.edu.StudyTaskManager;
+import com.jetbrains.python.edu.actions.StudyReloadCourseAction;
import com.jetbrains.python.edu.course.Course;
import com.jetbrains.python.edu.course.Lesson;
import com.jetbrains.python.edu.course.LessonInfo;
@@ -17,6 +18,8 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
import java.util.List;
public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware {
@@ -41,7 +44,15 @@ public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware {
contentPanel.add(new JLabel(authorLabel));
contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
contentPanel.add(new JLabel(description));
+ contentPanel.add(Box.createRigidArea(new Dimension(0, 10)));
+ JButton reloadCourseButton = new JButton("reload course");
+ reloadCourseButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ StudyReloadCourseAction.reloadCourse(project);
+ }
+ });
+ contentPanel.add(reloadCourseButton);
int taskNum = 0;
int taskSolved = 0;
int lessonsCompleted = 0;
diff --git a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
index eed232dcd3c7..939c84129455 100644
--- a/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
+++ b/python/edu/resources/idea/PyCharmEduApplicationInfo.xml
@@ -19,5 +19,5 @@
<feedback eap-url="http://www.jetbrains.com/feedback/feedback.jsp?product=PyCharm&amp;build=$BUILD&amp;timezone=$TIMEZONE&amp;eval=$EVAL"
release-url="http://www.jetbrains.com/feedback/feedback.jsp?product=PyCharm&amp;build=$BUILD&amp;timezone=$TIMEZONE&amp;eval=$EVAL"/>
- <help file="pycharm-eduhelp.jar" root="pycharm"/>
+ <help file="pycharm-eduhelp.jar" root="pycharm-edu"/>
</component>
diff --git a/python/helpers/packaging_tool.py b/python/helpers/packaging_tool.py
index 9101a16be1b3..c66cbcba2fe2 100644
--- a/python/helpers/packaging_tool.py
+++ b/python/helpers/packaging_tool.py
@@ -21,7 +21,7 @@ def exit(retcode):
def usage():
- sys.stderr.write('Usage: packaging_tool.py <list|search|install|uninstall|pyvenv>\n')
+ sys.stderr.write('Usage: packaging_tool.py <list|install|uninstall|pyvenv>\n')
sys.stderr.flush()
exit(ERROR_WRONG_USAGE)
@@ -58,13 +58,6 @@ def do_install(pkgs):
error_no_pip()
return pip.main(['install'] + pkgs)
-def do_search(pkgs):
- try:
- import pip
- except ImportError:
- error_no_pip()
- return pip.main(['search'] + pkgs)
-
def do_uninstall(pkgs):
try:
@@ -122,11 +115,6 @@ def main():
if len(sys.argv) != 2:
usage()
do_list()
- elif cmd == 'search':
- if len(sys.argv) < 2:
- usage()
- pkgs = sys.argv[2:]
- do_search(pkgs)
elif cmd == 'install':
if len(sys.argv) < 2:
usage()
diff --git a/python/helpers/pycharm/_bdd_utils.py b/python/helpers/pycharm/_bdd_utils.py
index eea1bebf1654..65d0f93102b9 100644
--- a/python/helpers/pycharm/_bdd_utils.py
+++ b/python/helpers/pycharm/_bdd_utils.py
@@ -3,7 +3,7 @@
Tools for running BDD frameworks in python.
You probably need to extend BddRunner (see its doc).
-You may also need "get_path_by_args" that gets folder (current or passed as first argument)
+You may also need "get_what_to_run_by_env" that gets folder (current or passed as first argument)
"""
import os
import time
@@ -29,20 +29,33 @@ def fix_win_drive(feature_path):
os.chdir(feature_disk)
-def get_path_by_args(arguments):
+def get_what_to_run_by_env(environment):
"""
- :type arguments list
- :param arguments: arguments (sys.argv)
- :return: tuple (base_dir, what_to_run) where dir is current or first argument from argv, checking it exists
- :rtype tuple of str
+ :type environment dict
+ :param environment: os.environment (files and folders should be separated with | and passed to PY_STUFF_TO_RUN).
+ Scenarios optionally could be passed as SCENARIOS (names or order numbers, depends on runner)
+ :return: tuple (base_dir, scenarios[], what_to_run(list of feature files or folders))) where dir is current or first argument from env, checking it exists
+ :rtype tuple of (str, iterable)
"""
- what_to_run = arguments[1] if len(arguments) > 1 else "."
- base_dir = what_to_run
- assert os.path.exists(what_to_run), "{} does not exist".format(what_to_run)
+ if "PY_STUFF_TO_RUN" not in environment:
+ what_to_run = ["."]
+ else:
+ what_to_run = str(environment["PY_STUFF_TO_RUN"]).split("|")
- if os.path.isfile(what_to_run):
- base_dir = os.path.dirname(what_to_run) # User may point to the file directly
- return base_dir, what_to_run
+ scenarios = []
+ if "SCENARIOS" in environment:
+ scenarios = str(environment["SCENARIOS"]).split("|")
+
+ if not what_to_run:
+ what_to_run = ["."]
+
+ for path in what_to_run:
+ assert os.path.exists(path), "{} does not exist".format(path)
+
+ base_dir = what_to_run[0]
+ if os.path.isfile(what_to_run[0]):
+ base_dir = os.path.dirname(what_to_run[0]) # User may point to the file directly
+ return base_dir, scenarios, what_to_run
class BddRunner(object):
diff --git a/python/helpers/pycharm/behave_runner.py b/python/helpers/pycharm/behave_runner.py
index 2ec649ea7c1d..99acfdb9ffeb 100644
--- a/python/helpers/pycharm/behave_runner.py
+++ b/python/helpers/pycharm/behave_runner.py
@@ -1,13 +1,14 @@
# coding=utf-8
"""
Behave BDD runner.
-*FIRST* param now: folder to search "features" for.
-Each "features" folder should have features and "steps" subdir.
+See _bdd_utils#get_path_by_env for information how to pass list of features here.
+Each feature could be file, folder with feature files or folder with "features" subfolder
Other args are tag expressionsin format (--tags=.. --tags=..).
See https://pythonhosted.org/behave/behave.html#tag-expression
"""
import functools
+import glob
import sys
import os
import traceback
@@ -15,6 +16,7 @@ import traceback
from behave.formatter.base import Formatter
from behave.model import Step, ScenarioOutline, Feature, Scenario
from behave.tag_expression import TagExpression
+import re
import _bdd_utils
@@ -185,16 +187,19 @@ class _BehaveRunner(_bdd_utils.BddRunner):
self.__real_runner.run()
- def __filter_scenarios_by_tag(self, scenario):
+ def __filter_scenarios_by_args(self, scenario):
"""
- Filters out scenarios that should be skipped by tags
+ Filters out scenarios that should be skipped by tags or scenario names
:param scenario scenario to check
:return true if should pass
"""
assert isinstance(scenario, Scenario), scenario
expected_tags = self.__config.tags
+ scenario_name_re = self.__config.name_re
+ if scenario_name_re and not scenario_name_re.match(scenario.name):
+ return False
if not expected_tags:
- return True # No tags are required
+ return True # No tags nor names are required
return isinstance(expected_tags, TagExpression) and expected_tags.check(scenario.tags)
@@ -213,7 +218,7 @@ class _BehaveRunner(_bdd_utils.BddRunner):
scenarios.extend(scenario.scenarios)
else:
scenarios.append(scenario)
- feature.scenarios = filter(self.__filter_scenarios_by_tag, scenarios)
+ feature.scenarios = filter(self.__filter_scenarios_by_args, scenarios)
return features_to_run
@@ -230,18 +235,27 @@ if __name__ == "__main__":
command_args = list(filter(None, sys.argv[1:]))
if command_args:
_bdd_utils.fix_win_drive(command_args[0])
+ (base_dir, scenario_names, what_to_run) = _bdd_utils.get_what_to_run_by_env(os.environ)
+
+ for scenario_name in scenario_names:
+ command_args += ["-n", re.escape(scenario_name)] # TODO : rewite pythonic
+
my_config = configuration.Configuration(command_args=command_args)
formatters.register_as(_Null, "com.intellij.python.null")
my_config.format = ["com.intellij.python.null"] # To prevent output to stdout
my_config.reporters = [] # To prevent summary to stdout
my_config.stdout_capture = False # For test output
my_config.stderr_capture = False # For test output
- (base_dir, what_to_run) = _bdd_utils.get_path_by_args(sys.argv)
- if not my_config.paths: # No path provided, trying to load dit manually
- if os.path.isfile(what_to_run): # File is provided, load it
- my_config.paths = [what_to_run]
- else: # Dir is provided, find subdirs ro run
- my_config.paths = _get_dirs_to_run(base_dir)
+ features = set()
+ for feature in what_to_run:
+ if os.path.isfile(feature) or glob.glob(
+ os.path.join(feature, "*.feature")): # File of folder with "features" provided, load it
+ features.add(feature)
+ elif os.path.isdir(feature):
+ features |= set(_get_dirs_to_run(feature)) # Find "features" subfolder
+ my_config.paths = list(features)
+ if what_to_run and not my_config.paths:
+ raise Exception("Nothing to run in {}".format(what_to_run))
_BehaveRunner(my_config, base_dir).run()
diff --git a/python/helpers/pycharm/lettuce_runner.py b/python/helpers/pycharm/lettuce_runner.py
index f0a4b5dbb873..2f64afc956d9 100644
--- a/python/helpers/pycharm/lettuce_runner.py
+++ b/python/helpers/pycharm/lettuce_runner.py
@@ -2,13 +2,14 @@
"""
BDD lettuce framework runner
TODO: Support other params (like tags) as well.
-Supports only 1 param now: folder to search "features" for.
+Supports only 2 params now: folder to search "features" for or file and "-s scenario_index"
"""
+import argparse
+import os
import _bdd_utils
__author__ = 'Ilya.Kazakevich'
from lettuce.exceptions import ReasonToFail
-import sys
import lettuce
from lettuce import core
@@ -18,31 +19,43 @@ class _LettuceRunner(_bdd_utils.BddRunner):
Lettuce runner (BddRunner for lettuce)
"""
- def __init__(self, base_dir, what_to_run):
+ def __init__(self, base_dir, what_to_run, scenarios):
"""
+ :param scenarios scenario numbers to run
+ :type scenarios list
:param base_dir base directory to run tests in
:type base_dir: str
:param what_to_run folder or file to run
:type what_to_run str
+
"""
super(_LettuceRunner, self).__init__(base_dir)
- self.__runner = lettuce.Runner(what_to_run)
+ self.__runner = lettuce.Runner(what_to_run, ",".join(scenarios))
def _get_features_to_run(self):
super(_LettuceRunner, self)._get_features_to_run()
- if self.__runner.single_feature: # We need to run one and only one feature
- return [core.Feature.from_file(self.__runner.single_feature)]
-
- # Find all features in dir
features = []
- for feature_file in self.__runner.loader.find_feature_files():
- feature = core.Feature.from_file(feature_file)
- assert isinstance(feature, core.Feature), feature
- # TODO: cut out due to https://github.com/gabrielfalcao/lettuce/issues/451 Fix when this issue fixed
- feature.scenarios = filter(lambda s: not s.outlines, feature.scenarios)
- if feature.scenarios:
- features.append(feature)
+ if self.__runner.single_feature: # We need to run one and only one feature
+ features = [core.Feature.from_file(self.__runner.single_feature)]
+ else:
+ # Find all features in dir
+ for feature_file in self.__runner.loader.find_feature_files():
+ feature = core.Feature.from_file(feature_file)
+ assert isinstance(feature, core.Feature), feature
+ # TODO: cut out due to https://github.com/gabrielfalcao/lettuce/issues/451 Fix when this issue fixed
+ feature.scenarios = filter(lambda s: not s.outlines, feature.scenarios)
+ if feature.scenarios:
+ features.append(feature)
+
+ # Choose only selected scenarios
+ if self.__runner.scenarios:
+ for feature in features:
+ filtered_feature_scenarios = []
+ for index in [i - 1 for i in self.__runner.scenarios]: # decrease index by 1
+ if index < len(feature.scenarios):
+ filtered_feature_scenarios.append(feature.scenarios[index])
+ feature.scenarios = filtered_feature_scenarios
return features
def _run_tests(self):
@@ -108,6 +121,8 @@ class _LettuceRunner(_bdd_utils.BddRunner):
if __name__ == "__main__":
- (base_dir, what_to_run) = _bdd_utils.get_path_by_args(sys.argv)
- _bdd_utils.fix_win_drive(what_to_run)
- _LettuceRunner(base_dir, what_to_run).run() \ No newline at end of file
+ (base_dir, scenarios, what_to_run) = _bdd_utils.get_what_to_run_by_env(os.environ)
+ if len(what_to_run) > 1:
+ raise Exception("Lettuce can't run more than one file now")
+ _bdd_utils.fix_win_drive(what_to_run[0])
+ _LettuceRunner(base_dir, what_to_run[0], scenarios).run() \ No newline at end of file
diff --git a/python/helpers/pycharm_generator_utils/clr_tools.py b/python/helpers/pycharm_generator_utils/clr_tools.py
index 4c273ae7d4d5..f4c3cfbf90f0 100644
--- a/python/helpers/pycharm_generator_utils/clr_tools.py
+++ b/python/helpers/pycharm_generator_utils/clr_tools.py
@@ -18,10 +18,14 @@ def get_namespace_by_name(object_name):
first_part = parts[0]
remain_part = parts[2]
- while remain_part and type(_get_attr_by_name(imported_object, remain_part)) is type: # While we are in class
+ while remain_part and type(_get_attr_by_name(imported_object, remain_part)) is type: # While we are in class
remain_part = remain_part.rpartition(".")[0]
- return first_part + "." + remain_part if remain_part else first_part
+ if remain_part:
+ return first_part + "." + remain_part
+ else:
+ return first_part
+
def _import_first(object_name):
"""
@@ -33,13 +37,12 @@ def _import_first(object_name):
"""
while object_name:
try:
- return (__import__(object_name), object_name)
+ return (__import__(object_name, globals=[], locals=[], fromlist=[]), object_name)
except ImportError:
- object_name = object_name.rpartition(".")[0] # Remove rightest part
+ object_name = object_name.rpartition(".")[0] # Remove rightest part
raise Exception("No module name found in name " + object_name)
-
def _get_attr_by_name(obj, name):
"""
Accepts chain of attributes in dot notation like "some.property.name" and gets them on object
diff --git a/python/helpers/pydev/django_debug.py b/python/helpers/pydev/django_debug.py
deleted file mode 100644
index 2b17864db47e..000000000000
--- a/python/helpers/pydev/django_debug.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import inspect
-from django_frame import DjangoTemplateFrame
-from pydevd_comm import CMD_SET_BREAK
-from pydevd_constants import DJANGO_SUSPEND, GetThreadId, DictContains
-from pydevd_file_utils import NormFileToServer
-from pydevd_breakpoints import LineBreakpoint
-import pydevd_vars
-import traceback
-
-class DjangoLineBreakpoint(LineBreakpoint):
-
- def __init__(self, file, line, condition, func_name, expression):
- self.file = file
- LineBreakpoint.__init__(self, line, condition, func_name, expression)
-
- def is_triggered(self, template_frame_file, template_frame_line):
- return self.file == template_frame_file and self.line == template_frame_line
-
- def __str__(self):
- return "DjangoLineBreakpoint: %s-%d" %(self.file, self.line)
-
-
-def inherits(cls, *names):
- if cls.__name__ in names:
- return True
- inherits_node = False
- for base in inspect.getmro(cls):
- if base.__name__ in names:
- inherits_node = True
- break
- return inherits_node
-
-
-def is_django_render_call(frame):
- try:
- name = frame.f_code.co_name
- if name != 'render':
- return False
-
- if not DictContains(frame.f_locals, 'self'):
- return False
-
- cls = frame.f_locals['self'].__class__
-
- inherits_node = inherits(cls, 'Node')
-
- if not inherits_node:
- return False
-
- clsname = cls.__name__
- return clsname != 'TextNode' and clsname != 'NodeList'
- except:
- traceback.print_exc()
- return False
-
-
-def is_django_context_get_call(frame):
- try:
- if not DictContains(frame.f_locals, 'self'):
- return False
-
- cls = frame.f_locals['self'].__class__
-
- return inherits(cls, 'BaseContext')
- except:
- traceback.print_exc()
- return False
-
-
-def is_django_resolve_call(frame):
- try:
- name = frame.f_code.co_name
- if name != '_resolve_lookup':
- return False
-
- if not DictContains(frame.f_locals, 'self'):
- return False
-
- cls = frame.f_locals['self'].__class__
-
- clsname = cls.__name__
- return clsname == 'Variable'
- except:
- traceback.print_exc()
- return False
-
-
-def is_django_suspended(thread):
- return thread.additionalInfo.suspend_type == DJANGO_SUSPEND
-
-
-def suspend_django(py_db_frame, mainDebugger, thread, frame, cmd=CMD_SET_BREAK):
- frame = DjangoTemplateFrame(frame)
-
- if frame.f_lineno is None:
- return None
-
- #try:
- # if thread.additionalInfo.filename == frame.f_code.co_filename and thread.additionalInfo.line == frame.f_lineno:
- # return None # don't stay twice on the same line
- #except AttributeError:
- # pass
-
- pydevd_vars.addAdditionalFrameById(GetThreadId(thread), {id(frame): frame})
-
- py_db_frame.setSuspend(thread, cmd)
- thread.additionalInfo.suspend_type = DJANGO_SUSPEND
-
- thread.additionalInfo.filename = frame.f_code.co_filename
- thread.additionalInfo.line = frame.f_lineno
-
- return frame
-
-
-def find_django_render_frame(frame):
- while frame is not None and not is_django_render_call(frame):
- frame = frame.f_back
-
- return frame
-
-
-
-
-
diff --git a/python/helpers/pydev/django_frame.py b/python/helpers/pydev/django_frame.py
deleted file mode 100644
index 4181572aed3c..000000000000
--- a/python/helpers/pydev/django_frame.py
+++ /dev/null
@@ -1,132 +0,0 @@
-from pydevd_file_utils import GetFileNameAndBaseFromFile
-import pydev_log
-import traceback
-from pydevd_constants import DictContains
-
-def read_file(filename):
- f = open(filename, "r")
- try:
- s = f.read()
- finally:
- f.close()
- return s
-
-
-def offset_to_line_number(text, offset):
- curLine = 1
- curOffset = 0
- while curOffset < offset:
- if curOffset == len(text):
- return -1
- c = text[curOffset]
- if c == '\n':
- curLine += 1
- elif c == '\r':
- curLine += 1
- if curOffset < len(text) and text[curOffset + 1] == '\n':
- curOffset += 1
-
- curOffset += 1
-
- return curLine
-
-
-def get_source(frame):
- try:
- node = frame.f_locals['self']
- if hasattr(node, 'source'):
- return node.source
- else:
- pydev_log.error_once(
- "WARNING: Template path is not available. Please set TEMPLATE_DEBUG=True "
- "in your settings.py to make django template breakpoints working")
- return None
-
- except:
- pydev_log.debug(traceback.format_exc())
- return None
-
-
-def get_template_file_name(frame):
- try:
- source = get_source(frame)
- if source is None:
- pydev_log.debug("Source is None\n")
- return None
- fname = source[0].name
-
- if fname == '<unknown source>':
- pydev_log.debug("Source name is %s\n" % fname)
- return None
- else:
- filename, base = GetFileNameAndBaseFromFile(fname)
- return filename
- except:
- pydev_log.debug(traceback.format_exc())
- return None
-
-
-def get_template_line(frame, template_frame_file):
- source = get_source(frame)
- try:
- return offset_to_line_number(read_file(template_frame_file), source[1][0])
- except:
- return None
-
-
-class DjangoTemplateFrame:
- def __init__(
- self,
- frame,
- template_frame_file=None,
- template_frame_line=None):
-
- if template_frame_file is None:
- template_frame_file = get_template_file_name(frame)
-
- self.back_context = frame.f_locals['context']
- self.f_code = FCode('Django Template', template_frame_file)
-
- if template_frame_line is None:
- template_frame_line = get_template_line(frame, template_frame_file)
- self.f_lineno = template_frame_line
-
- self.f_back = frame
- self.f_globals = {}
- self.f_locals = self.collect_context()
- self.f_trace = None
-
- def collect_context(self):
- res = {}
- try:
- for d in self.back_context.dicts:
- res.update(d)
- except AttributeError:
- pass
- return res
-
- def changeVariable(self, name, value):
- for d in self.back_context.dicts:
- if DictContains(d, name):
- d[name] = value
- self.f_locals[name] = value
-
-
-class FCode:
- def __init__(self, name, filename):
- self.co_name = name
- self.co_filename = filename
-
-
-def is_django_exception_break_context(frame):
- try:
- return frame.f_code.co_name in ['_resolve_lookup', 'find_template']
- except:
- return False
-
-
-def just_raised(trace):
- if trace is None:
- return False
- return trace.tb_next is None
-
diff --git a/python/helpers/pydev/pydev_log.py b/python/helpers/pydev/pydev_log.py
index 229784b76a91..b5e65b3102e6 100644
--- a/python/helpers/pydev/pydev_log.py
+++ b/python/helpers/pydev/pydev_log.py
@@ -2,6 +2,8 @@ import sys
from pydevd_constants import DebugInfoHolder
from pydevd_constants import DictContains
+import traceback
+
WARN_ONCE_MAP = {}
def stderr_write(message):
@@ -18,11 +20,16 @@ def warn(message):
if DebugInfoHolder.DEBUG_TRACE_LEVEL>1:
stderr_write(message)
+
def info(message):
stderr_write(message)
-def error(message):
+
+def error(message, tb=False):
stderr_write(message)
+ if tb:
+ traceback.print_exc()
+
def error_once(message):
if not DictContains(WARN_ONCE_MAP, message):
diff --git a/python/helpers/pydev/pydev_monkey_qt.py b/python/helpers/pydev/pydev_monkey_qt.py
index 9c62686173dd..2675e9e55708 100644
--- a/python/helpers/pydev/pydev_monkey_qt.py
+++ b/python/helpers/pydev/pydev_monkey_qt.py
@@ -11,7 +11,7 @@ def set_trace_in_qt():
_patched_qt = False
def patch_qt():
'''
- This method patches qt (PySide or PyQt4) so that we have hooks to set the tracing for QThread.
+ This method patches qt (PySide, PyQt4, PyQt5) so that we have hooks to set the tracing for QThread.
'''
# Avoid patching more than once
@@ -27,7 +27,10 @@ def patch_qt():
try:
from PyQt4 import QtCore
except:
- return
+ try:
+ from PyQt5 import QtCore
+ except:
+ return
_original_thread_init = QtCore.QThread.__init__
_original_runnable_init = QtCore.QRunnable.__init__
diff --git a/python/helpers/pydev/pydev_run_in_console.py b/python/helpers/pydev/pydev_run_in_console.py
index 1b8e1d230175..731ead67115e 100644
--- a/python/helpers/pydev/pydev_run_in_console.py
+++ b/python/helpers/pydev/pydev_run_in_console.py
@@ -2,6 +2,7 @@
from pydevconsole import *
import pydev_imports
+from pydevd_utils import save_main_module
def run_file(file, globals=None, locals=None):
@@ -11,22 +12,8 @@ def run_file(file, globals=None, locals=None):
file = new_target
if globals is None:
- # patch provided by: Scott Schlesier - when script is run, it does not
- # use globals from pydevd:
- # This will prevent the pydevd script from contaminating the namespace for the script to be debugged
-
- # pretend pydevd is not the main module, and
- # convince the file to be debugged that it was loaded as main
- sys.modules['pydevd'] = sys.modules['__main__']
- sys.modules['pydevd'].__name__ = 'pydevd'
-
- from imp import new_module
- m = new_module('__main__')
- sys.modules['__main__'] = m
- if hasattr(sys.modules['pydevd'], '__loader__'):
- setattr(m, '__loader__', getattr(sys.modules['pydevd'], '__loader__'))
-
- m.__file__ = file
+ m = save_main_module(file, 'pydev_run_in_console')
+
globals = m.__dict__
try:
globals['__builtins__'] = __builtins__
diff --git a/python/helpers/pydev/pydevconsole.py b/python/helpers/pydev/pydevconsole.py
index 8d4375f5a5aa..444aa2d1c48b 100644
--- a/python/helpers/pydev/pydevconsole.py
+++ b/python/helpers/pydev/pydevconsole.py
@@ -80,10 +80,18 @@ try:
from pydev_imports import execfile
__builtin__.execfile = execfile
-
except:
pass
+# Pull in runfile, the interface to UMD that wraps execfile
+from pydev_umd import runfile, _set_globals_function
+try:
+ import builtins
+ builtins.runfile = runfile
+except:
+ import __builtin__
+ __builtin__.runfile = runfile
+
#=======================================================================================================================
# InterpreterInterface
@@ -264,6 +272,9 @@ def start_server(host, port, interpreter):
sys.stderr.write('Error starting server with host: %s, port: %s, client_port: %s\n' % (host, port, client_port))
raise
+ # Tell UMD the proper default namespace
+ _set_globals_function(interpreter.getNamespace)
+
server.register_function(interpreter.execLine)
server.register_function(interpreter.execMultipleLines)
server.register_function(interpreter.getCompletions)
diff --git a/python/helpers/pydev/pydevd.py b/python/helpers/pydev/pydevd.py
index 9d0da096c07d..8d68cea9876c 100644
--- a/python/helpers/pydev/pydevd.py
+++ b/python/helpers/pydev/pydevd.py
@@ -3,12 +3,15 @@ from __future__ import nested_scopes # Jython 2.1 support
from pydevd_constants import * # @UnusedWildImport
import pydev_monkey_qt
+from pydevd_utils import save_main_module
+
pydev_monkey_qt.patch_qt()
import traceback
-from django_debug import DjangoLineBreakpoint
-from pydevd_frame import add_exception_to_frame
+from pydevd_plugin_utils import PluginManager
+
+from pydevd_frame_utils import add_exception_to_frame
import pydev_imports
from pydevd_breakpoints import * #@UnusedWildImport
import fix_getpass
@@ -110,13 +113,18 @@ DONT_TRACE = {
'linecache.py':1,
'threading.py':1,
+ # thirs party libs that we don't want to trace
+ 'pluginbase.py':1,
+ 'pkgutil_old.py':1,
+ 'uuid_old.py':1,
+
#things from pydev that we don't want to trace
'_pydev_execfile.py':1,
'_pydev_jython_execfile.py':1,
'_pydev_threading':1,
'_pydev_Queue':1,
'django_debug.py':1,
- 'django_frame.py':1,
+ 'jinja2_debug.py':1,
'pydev_log.py':1,
'pydev_monkey.py':1 ,
'pydevd.py':1 ,
@@ -301,17 +309,15 @@ class PyDB:
self._cmd_queue = {} # the hash of Queues. Key is thread id, value is thread
self.breakpoints = {}
- self.django_breakpoints = {}
self.file_to_id_to_line_breakpoint = {}
- self.file_to_id_to_django_breakpoint = {}
+ self.file_to_id_to_plugin_breakpoint = {}
# Note: breakpoints dict should not be mutated: a copy should be created
# and later it should be assigned back (to prevent concurrency issues).
self.break_on_uncaught_exceptions = {}
self.break_on_caught_exceptions = {}
- self.django_exception_break = {}
self.readyToRun = False
self._main_lock = _pydev_thread.allocate_lock()
self._lock_running_thread_ids = _pydev_thread.allocate_lock()
@@ -344,6 +350,8 @@ class PyDB:
# This attribute holds the file-> lines which have an @IgnoreException.
self.filename_to_lines_where_exceptions_are_ignored = {}
+ #working with plugins
+ self.plugin = PluginManager(self)
def haveAliveThreads(self):
for t in threadingEnumerate():
@@ -568,12 +576,16 @@ class PyDB:
notify_on_terminate,
notify_on_first_raise_only,
):
- eb = ExceptionBreakpoint(
- exception,
- notify_always,
- notify_on_terminate,
- notify_on_first_raise_only,
- )
+ try:
+ eb = ExceptionBreakpoint(
+ exception,
+ notify_always,
+ notify_on_terminate,
+ notify_on_first_raise_only,
+ )
+ except ImportError:
+ pydev_log.error("Error unable to add break on exception for: %s (exception could not be imported)\n" % (exception,))
+ return None
if eb.notify_on_terminate:
cp = self.break_on_uncaught_exceptions.copy()
@@ -839,15 +851,22 @@ class PyDB:
if len(expression) <= 0 or expression is None or expression == "None":
expression = None
+ supported_type = False
if type == 'python-line':
breakpoint = LineBreakpoint(line, condition, func_name, expression)
breakpoints = self.breakpoints
file_to_id_to_breakpoint = self.file_to_id_to_line_breakpoint
- elif type == 'django-line':
- breakpoint = DjangoLineBreakpoint(file, line, condition, func_name, expression)
- breakpoints = self.django_breakpoints
- file_to_id_to_breakpoint = self.file_to_id_to_django_breakpoint
+ supported_type = True
else:
+ result = self.plugin.add_breakpoint('add_line_breakpoint', self, type, file, line, condition, expression, func_name)
+ if result is not None:
+ supported_type = True
+ breakpoint, breakpoints = result
+ file_to_id_to_breakpoint = self.file_to_id_to_plugin_breakpoint
+ else:
+ supported_type = False
+
+ if not supported_type:
raise NameError(type)
if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
@@ -880,27 +899,31 @@ class PyDB:
pydev_log.error('Error removing breakpoint. Expected breakpoint_id to be an int. Found: %s' % (breakpoint_id,))
else:
+ file_to_id_to_breakpoint = None
if breakpoint_type == 'python-line':
breakpoints = self.breakpoints
file_to_id_to_breakpoint = self.file_to_id_to_line_breakpoint
- elif breakpoint_type == 'django-line':
- breakpoints = self.django_breakpoints
- file_to_id_to_breakpoint = self.file_to_id_to_django_breakpoint
else:
- raise NameError(breakpoint_type)
+ result = self.plugin.get_breakpoints(self, breakpoint_type)
+ if result is not None:
+ file_to_id_to_breakpoint = self.file_to_id_to_plugin_breakpoint
+ breakpoints = result
- try:
- id_to_pybreakpoint = file_to_id_to_breakpoint.get(file, {})
- if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
- existing = id_to_pybreakpoint[breakpoint_id]
- sys.stderr.write('Removed breakpoint:%s - line:%s - func_name:%s (id: %s)\n' % (
- file, existing.line, existing.func_name.encode('utf-8'), breakpoint_id))
+ if file_to_id_to_breakpoint is None:
+ pydev_log.error('Error removing breakpoint. Cant handle breakpoint of type %s' % breakpoint_type)
+ else:
+ try:
+ id_to_pybreakpoint = file_to_id_to_breakpoint.get(file, {})
+ if DebugInfoHolder.DEBUG_TRACE_BREAKPOINTS > 0:
+ existing = id_to_pybreakpoint[breakpoint_id]
+ sys.stderr.write('Removed breakpoint:%s - line:%s - func_name:%s (id: %s)\n' % (
+ file, existing.line, existing.func_name.encode('utf-8'), breakpoint_id))
- del id_to_pybreakpoint[breakpoint_id]
- self.consolidate_breakpoints(file, id_to_pybreakpoint, breakpoints)
- except KeyError:
- pydev_log.error("Error removing breakpoint: Breakpoint id not found: %s id: %s. Available ids: %s\n" % (
- file, breakpoint_id, DictKeys(id_to_pybreakpoint)))
+ del id_to_pybreakpoint[breakpoint_id]
+ self.consolidate_breakpoints(file, id_to_pybreakpoint, breakpoints)
+ except KeyError:
+ pydev_log.error("Error removing breakpoint: Breakpoint id not found: %s id: %s. Available ids: %s\n" % (
+ file, breakpoint_id, DictKeys(id_to_pybreakpoint)))
elif cmd_id == CMD_EVALUATE_EXPRESSION or cmd_id == CMD_EXEC_EXPRESSION:
@@ -963,6 +986,8 @@ class PyDB:
notify_on_terminate=break_on_uncaught,
notify_on_first_raise_only=False,
)
+ if exception_breakpoint is None:
+ continue
added.append(exception_breakpoint)
self.update_after_exceptions_added(added)
@@ -1013,28 +1038,58 @@ class PyDB:
pass
elif cmd_id == CMD_ADD_EXCEPTION_BREAK:
- exception, notify_always, notify_on_terminate = text.split('\t', 2)
- exception_breakpoint = self.add_break_on_exception(
- exception,
- notify_always=int(notify_always) > 0,
- notify_on_terminate = int(notify_on_terminate) == 1,
- notify_on_first_raise_only=int(notify_always) == 2
- )
- self.update_after_exceptions_added([exception_breakpoint])
+ if text.find('\t') != -1:
+ exception, notify_always, notify_on_terminate = text.split('\t', 2)
+ else:
+ exception, notify_always, notify_on_terminate = text, 0, 0
+
+ if exception.find('-') != -1:
+ type, exception = exception.split('-')
+ else:
+ type = 'python'
+
+ if type == 'python':
+ exception_breakpoint = self.add_break_on_exception(
+ exception,
+ notify_always=int(notify_always) > 0,
+ notify_on_terminate = int(notify_on_terminate) == 1,
+ notify_on_first_raise_only=int(notify_always) == 2
+ )
+
+ if exception_breakpoint is not None:
+ self.update_after_exceptions_added([exception_breakpoint])
+ else:
+ supported_type = self.plugin.add_breakpoint('add_exception_breakpoint', self, type, exception)
+
+ if not supported_type:
+ raise NameError(type)
+
+
elif cmd_id == CMD_REMOVE_EXCEPTION_BREAK:
exception = text
- try:
- cp = self.break_on_uncaught_exceptions.copy()
- DictPop(cp, exception, None)
- self.break_on_uncaught_exceptions = cp
+ if exception.find('-') != -1:
+ type, exception = exception.split('-')
+ else:
+ type = 'python'
- cp = self.break_on_caught_exceptions.copy()
- DictPop(cp, exception, None)
- self.break_on_caught_exceptions = cp
- except:
- pydev_log.debug("Error while removing exception %s"%sys.exc_info()[0]);
- update_exception_hook(self)
+ if type == 'python':
+ try:
+ cp = self.break_on_uncaught_exceptions.copy()
+ DictPop(cp, exception, None)
+ self.break_on_uncaught_exceptions = cp
+
+ cp = self.break_on_caught_exceptions.copy()
+ DictPop(cp, exception, None)
+ self.break_on_caught_exceptions = cp
+ except:
+ pydev_log.debug("Error while removing exception %s"%sys.exc_info()[0])
+ update_exception_hook(self)
+ else:
+ supported_type = self.plugin.remove_exception_breakpoint(self, type, exception)
+
+ if not supported_type:
+ raise NameError(type)
elif cmd_id == CMD_LOAD_SOURCE:
path = text
@@ -1048,16 +1103,13 @@ class PyDB:
elif cmd_id == CMD_ADD_DJANGO_EXCEPTION_BREAK:
exception = text
- self.django_exception_break[exception] = True
- self.setTracingForUntracedContexts()
+ self.plugin.add_breakpoint('add_exception_breakpoint', self, 'django', exception)
+
elif cmd_id == CMD_REMOVE_DJANGO_EXCEPTION_BREAK:
exception = text
- try:
- del self.django_exception_break[exception]
- except :
- pass
+ self.plugin.remove_exception_breakpoint(self, 'django', exception)
elif cmd_id == CMD_EVALUATE_CONSOLE_EXPRESSION:
# Command which takes care for the debug console communication
@@ -1492,22 +1544,7 @@ class PyDB:
file = new_target
if globals is None:
- # patch provided by: Scott Schlesier - when script is run, it does not
- # use globals from pydevd:
- # This will prevent the pydevd script from contaminating the namespace for the script to be debugged
-
- # pretend pydevd is not the main module, and
- # convince the file to be debugged that it was loaded as main
- sys.modules['pydevd'] = sys.modules['__main__']
- sys.modules['pydevd'].__name__ = 'pydevd'
-
- from imp import new_module
- m = new_module('__main__')
- sys.modules['__main__'] = m
- if hasattr(sys.modules['pydevd'], '__loader__'):
- setattr(m, '__loader__', getattr(sys.modules['pydevd'], '__loader__'))
-
- m.__file__ = file
+ m = save_main_module(file, 'pydevd')
globals = m.__dict__
try:
globals['__builtins__'] = __builtins__
@@ -1546,8 +1583,6 @@ class PyDB:
pydev_imports.execfile(file, globals, locals) # execute the script
- return globals
-
def exiting(self):
sys.stdout.flush()
sys.stderr.flush()
@@ -2061,10 +2096,6 @@ if __name__ == '__main__':
debugger = PyDB()
- if setup['cmd-line']:
- debugger.cmd_line = True
-
-
if fix_app_engine_debug:
sys.stderr.write("pydev debugger: google app engine integration enabled\n")
curr_dir = os.path.dirname(__file__)
diff --git a/python/helpers/pydev/pydevd_breakpoints.py b/python/helpers/pydev/pydevd_breakpoints.py
index 1171157257e9..693823917a2f 100644
--- a/python/helpers/pydev/pydevd_breakpoints.py
+++ b/python/helpers/pydev/pydevd_breakpoints.py
@@ -40,8 +40,8 @@ class ExceptionBreakpoint:
def __str__(self):
return self.qname
-class LineBreakpoint:
+class LineBreakpoint(object):
def __init__(self, line, condition, func_name, expression):
self.line = line
self.condition = condition
diff --git a/python/helpers/pydev/pydevd_constants.py b/python/helpers/pydev/pydevd_constants.py
index e878d3b48ead..5e7a7a926bfb 100644
--- a/python/helpers/pydev/pydevd_constants.py
+++ b/python/helpers/pydev/pydevd_constants.py
@@ -5,7 +5,6 @@ STATE_RUN = 1
STATE_SUSPEND = 2
PYTHON_SUSPEND = 1
-DJANGO_SUSPEND = 2
try:
__setFalse = False
diff --git a/python/helpers/pydev/pydevd_frame.py b/python/helpers/pydev/pydevd_frame.py
index 5d1e78458391..922133ba1512 100644
--- a/python/helpers/pydev/pydevd_frame.py
+++ b/python/helpers/pydev/pydevd_frame.py
@@ -3,18 +3,15 @@ import os.path
import re
import traceback # @Reimport
-from django_debug import find_django_render_frame
-from django_debug import is_django_render_call, is_django_suspended, suspend_django, is_django_resolve_call, is_django_context_get_call
-from django_frame import DjangoTemplateFrame
-from django_frame import is_django_exception_break_context
-from django_frame import just_raised, get_template_file_name, get_template_line
import pydev_log
from pydevd_breakpoints import get_exception_breakpoint, get_exception_name
-from pydevd_comm import CMD_ADD_DJANGO_EXCEPTION_BREAK, \
- CMD_STEP_CAUGHT_EXCEPTION, CMD_STEP_RETURN, CMD_STEP_OVER, CMD_SET_BREAK, \
+from pydevd_comm import CMD_STEP_CAUGHT_EXCEPTION, CMD_STEP_RETURN, CMD_STEP_OVER, CMD_SET_BREAK, \
CMD_STEP_INTO, CMD_SMART_STEP_INTO, CMD_RUN_TO_LINE, CMD_SET_NEXT_STATEMENT
from pydevd_constants import * # @UnusedWildImport
from pydevd_file_utils import GetFilenameAndBase
+
+from pydevd_frame_utils import add_exception_to_frame, just_raised
+
try:
from pydevd_signature import sendSignatureCallTrace
except ImportError:
@@ -55,17 +52,6 @@ class PyDBFrame:
def doWaitSuspend(self, *args, **kwargs):
self._args[0].doWaitSuspend(*args, **kwargs)
- def _is_django_render_call(self, frame):
- try:
- return self._cached_is_django_render_call
- except:
- # Calculate lazily: note that a PyDBFrame always deals with the same
- # frame over and over, so, we can cache this.
- # -- although we can't cache things which change over time (such as
- # the breakpoints for the file).
- ret = self._cached_is_django_render_call = is_django_render_call(frame)
- return ret
-
def trace_exception(self, frame, event, arg):
if event == 'exception':
flag, frame = self.should_stop_on_exception(frame, event, arg)
@@ -97,22 +83,11 @@ class PyDBFrame:
flag = False
else:
try:
- if mainDebugger.django_exception_break and get_exception_name(exception) in [
- 'VariableDoesNotExist', 'TemplateDoesNotExist', 'TemplateSyntaxError'] \
- and just_raised(trace) and is_django_exception_break_context(frame):
-
- render_frame = find_django_render_frame(frame)
- if render_frame:
- suspend_frame = suspend_django(
- self, mainDebugger, thread, render_frame, CMD_ADD_DJANGO_EXCEPTION_BREAK)
-
- if suspend_frame:
- add_exception_to_frame(suspend_frame, (exception, value, trace))
- flag = True
- thread.additionalInfo.message = 'VariableDoesNotExist'
- suspend_frame.f_back = frame
- frame = suspend_frame
- except :
+ result = mainDebugger.plugin.exception_break(mainDebugger, self, frame, self._args, arg)
+ if result:
+ (flag, frame) = result
+
+ except:
flag = False
return flag, frame
@@ -253,7 +228,8 @@ class PyDBFrame:
sendSignatureCallTrace(main_debugger, frame, filename)
is_exception_event = event == 'exception'
- has_exception_breakpoints = main_debugger.break_on_caught_exceptions or main_debugger.django_exception_break
+ has_exception_breakpoints = main_debugger.break_on_caught_exceptions \
+ or main_debugger.plugin.has_exception_breaks(main_debugger)
if is_exception_event:
if has_exception_breakpoints:
@@ -293,9 +269,8 @@ class PyDBFrame:
can_skip = (step_cmd is None and stop_frame is None)\
or (step_cmd in (CMD_STEP_RETURN, CMD_STEP_OVER) and stop_frame is not frame)
- check_stop_on_django_render_call = main_debugger.django_breakpoints and self._is_django_render_call(frame)
- if check_stop_on_django_render_call:
- can_skip = False
+ if can_skip:
+ can_skip = not main_debugger.plugin.can_not_skip(main_debugger, self, frame)
# Let's check to see if we are in a function that has a breakpoint. If we don't have a breakpoint,
# we will return nothing for the next trace
@@ -334,29 +309,35 @@ class PyDBFrame:
try:
line = frame.f_lineno
-
-
flag = False
- if event == 'call' and info.pydev_state != STATE_SUSPEND and check_stop_on_django_render_call:
- flag, frame = self.should_stop_on_django_breakpoint(frame, event, arg)
-
#return is not taken into account for breakpoint hit because we'd have a double-hit in this case
#(one for the line and the other for the return).
- if not flag and event != 'return' and info.pydev_state != STATE_SUSPEND and breakpoints_for_file is not None\
- and DictContains(breakpoints_for_file, line):
- #ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
- # lets do the conditional stuff here
+ stop_info = {}
+ breakpoint = None
+ exist_result = False
+ stop_info['stop'] = False
+ if not flag and event != 'return' and info.pydev_state != STATE_SUSPEND and breakpoints_for_file is not None \
+ and DictContains(breakpoints_for_file, line):
breakpoint = breakpoints_for_file[line]
-
- stop = True
+ new_frame = frame
+ stop_info['stop'] = True
if step_cmd == CMD_STEP_OVER and stop_frame is frame and event in ('line', 'return'):
- stop = False #we don't stop on breakpoint if we have to stop by step-over (it will be processed later)
- else:
+ stop_info['stop'] = False #we don't stop on breakpoint if we have to stop by step-over (it will be processed later)
+ else:
+ result = main_debugger.plugin.get_breakpoint(main_debugger, self, frame, event, self._args)
+ if result:
+ exist_result = True
+ (flag, breakpoint, new_frame) = result
+
+ if breakpoint:
+ #ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
+ # lets do the conditional stuff here
+ if stop_info['stop'] or exist_result:
condition = breakpoint.condition
if condition is not None:
try:
- val = eval(condition, frame.f_globals, frame.f_locals)
+ val = eval(condition, new_frame.f_globals, new_frame.f_locals)
if not val:
return self.trace_dispatch
@@ -371,7 +352,7 @@ class PyDBFrame:
if not main_debugger.suspend_on_breakpoint_exception:
return self.trace_dispatch
else:
- stop = True
+ stop_info['stop'] = True
try:
additional_info = None
try:
@@ -395,18 +376,21 @@ class PyDBFrame:
except:
traceback.print_exc()
- if breakpoint.expression is not None:
- try:
+ if breakpoint.expression is not None:
try:
- val = eval(breakpoint.expression, frame.f_globals, frame.f_locals)
- except:
- val = sys.exc_info()[1]
- finally:
- if val is not None:
- thread.additionalInfo.message = val
-
- if stop:
- self.setSuspend(thread, CMD_SET_BREAK)
+ try:
+ val = eval(breakpoint.expression, new_frame.f_globals, new_frame.f_locals)
+ except:
+ val = sys.exc_info()[1]
+ finally:
+ if val is not None:
+ thread.additionalInfo.message = val
+ if stop_info['stop']:
+ self.setSuspend(thread, CMD_SET_BREAK)
+ elif flag:
+ result = main_debugger.plugin.suspend(main_debugger, thread, frame)
+ if result:
+ frame = result
# if thread has a suspend flag, we suspend with a busy wait
if info.pydev_state == STATE_SUSPEND:
@@ -419,8 +403,6 @@ class PyDBFrame:
#step handling. We stop when we hit the right frame
try:
- django_stop = False
-
should_skip = False
if pydevd_dont_trace.should_trace_hook is not None:
if not hasattr(self, 'should_skip'):
@@ -432,34 +414,18 @@ class PyDBFrame:
should_skip = self.should_skip
if should_skip:
- stop = False
+ stop_info['stop'] = False
elif step_cmd == CMD_STEP_INTO:
- stop = event in ('line', 'return')
-
- if is_django_suspended(thread):
- #django_stop = event == 'call' and is_django_render_call(frame)
- stop = stop and is_django_resolve_call(frame.f_back) and not is_django_context_get_call(frame)
- if stop:
- info.pydev_django_resolve_frame = 1 #we remember that we've go into python code from django rendering frame
+ stop_info['stop'] = event in ('line', 'return')
+ main_debugger.plugin.cmd_step_into(main_debugger, frame, event, self._args, stop_info)
elif step_cmd == CMD_STEP_OVER:
- if is_django_suspended(thread):
- django_stop = event == 'call' and self._is_django_render_call(frame)
-
- stop = False
- else:
- if event == 'return' and info.pydev_django_resolve_frame is not None and is_django_resolve_call(frame.f_back):
- #we return to Django suspend mode and should not stop before django rendering frame
- stop_frame = info.pydev_step_stop = info.pydev_django_resolve_frame
- info.pydev_django_resolve_frame = None
- thread.additionalInfo.suspend_type = DJANGO_SUSPEND
-
-
- stop = stop_frame is frame and event in ('line', 'return')
+ stop_info['stop'] = stop_frame is frame and event in ('line', 'return')
+ main_debugger.plugin.cmd_step_over(main_debugger, frame, event, self._args, stop_info)
elif step_cmd == CMD_SMART_STEP_INTO:
- stop = False
+ stop_info['stop'] = False
if info.pydev_smart_step_stop is frame:
info.pydev_func_name = None
info.pydev_smart_step_stop = None
@@ -472,13 +438,13 @@ class PyDBFrame:
curr_func_name = ''
if curr_func_name == info.pydev_func_name:
- stop = True
+ stop_info['stop'] = True
elif step_cmd == CMD_STEP_RETURN:
- stop = event == 'return' and stop_frame is frame
+ stop_info['stop'] = event == 'return' and stop_frame is frame
elif step_cmd == CMD_RUN_TO_LINE or step_cmd == CMD_SET_NEXT_STATEMENT:
- stop = False
+ stop_info['stop'] = False
if event == 'line' or event == 'exception':
#Yes, we can only act on line events (weird hum?)
@@ -493,50 +459,47 @@ class PyDBFrame:
if curr_func_name == info.pydev_func_name:
line = info.pydev_next_line
if frame.f_lineno == line:
- stop = True
+ stop_info['stop'] = True
else:
if frame.f_trace is None:
frame.f_trace = self.trace_dispatch
frame.f_lineno = line
frame.f_trace = None
- stop = True
+ stop_info['stop'] = True
else:
- stop = False
-
- if django_stop:
- frame = suspend_django(self, main_debugger, thread, frame)
- if frame:
- self.doWaitSuspend(thread, frame, event, arg)
- elif stop:
- #event is always == line or return at this point
- if event == 'line':
- self.setSuspend(thread, step_cmd)
- self.doWaitSuspend(thread, frame, event, arg)
- else: #return event
- back = frame.f_back
- if back is not None:
- #When we get to the pydevd run function, the debugging has actually finished for the main thread
- #(note that it can still go on for other threads, but for this one, we just make it finish)
- #So, just setting it to None should be OK
- base = basename(back.f_code.co_filename)
- if base == 'pydevd.py' and back.f_code.co_name == 'run':
- back = None
-
- elif base == 'pydevd_traceproperty.py':
- # We dont want to trace the return event of pydevd_traceproperty (custom property for debugging)
- #if we're in a return, we want it to appear to the user in the previous frame!
- return None
+ stop_info['stop'] = False
- if back is not None:
- #if we're in a return, we want it to appear to the user in the previous frame!
+ if True in DictIterValues(stop_info):
+ stopped_on_plugin = main_debugger.plugin.stop(main_debugger, frame, event, self._args, stop_info, arg, step_cmd)
+ if DictContains(stop_info, 'stop') and stop_info['stop'] and not stopped_on_plugin:
+ if event == 'line':
self.setSuspend(thread, step_cmd)
- self.doWaitSuspend(thread, back, event, arg)
- else:
- #in jython we may not have a back frame
- info.pydev_step_stop = None
- info.pydev_step_cmd = None
- info.pydev_state = STATE_RUN
+ self.doWaitSuspend(thread, frame, event, arg)
+ else: #return event
+ back = frame.f_back
+ if back is not None:
+ #When we get to the pydevd run function, the debugging has actually finished for the main thread
+ #(note that it can still go on for other threads, but for this one, we just make it finish)
+ #So, just setting it to None should be OK
+ base = basename(back.f_code.co_filename)
+ if base == 'pydevd.py' and back.f_code.co_name == 'run':
+ back = None
+
+ elif base == 'pydevd_traceproperty.py':
+ # We dont want to trace the return event of pydevd_traceproperty (custom property for debugging)
+ #if we're in a return, we want it to appear to the user in the previous frame!
+ return None
+
+ if back is not None:
+ #if we're in a return, we want it to appear to the user in the previous frame!
+ self.setSuspend(thread, step_cmd)
+ self.doWaitSuspend(thread, back, event, arg)
+ else:
+ #in jython we may not have a back frame
+ info.pydev_step_stop = None
+ info.pydev_step_cmd = None
+ info.pydev_state = STATE_RUN
except:
@@ -562,61 +525,4 @@ class PyDBFrame:
except ImportError:
if hasattr(sys, 'exc_clear'): #jython does not have it
sys.exc_clear() #don't keep the traceback
- pass #ok, psyco not available
-
- def should_stop_on_django_breakpoint(self, frame, event, arg):
- mainDebugger = self._args[0]
- thread = self._args[3]
- flag = False
- template_frame_file = get_template_file_name(frame)
-
- #pydev_log.debug("Django is rendering a template: %s\n" % template_frame_file)
-
- django_breakpoints_for_file = mainDebugger.django_breakpoints.get(template_frame_file)
- if django_breakpoints_for_file:
-
- #pydev_log.debug("Breakpoints for that file: %s\n" % django_breakpoints_for_file)
-
- template_frame_line = get_template_line(frame, template_frame_file)
-
- #pydev_log.debug("Tracing template line: %d\n" % template_frame_line)
-
- if DictContains(django_breakpoints_for_file, template_frame_line):
- django_breakpoint = django_breakpoints_for_file[template_frame_line]
-
- if django_breakpoint.is_triggered(template_frame_file, template_frame_line):
-
- #pydev_log.debug("Breakpoint is triggered.\n")
-
- flag = True
- new_frame = DjangoTemplateFrame(
- frame,
- template_frame_file=template_frame_file,
- template_frame_line=template_frame_line,
- )
-
- if django_breakpoint.condition is not None:
- try:
- val = eval(django_breakpoint.condition, new_frame.f_globals, new_frame.f_locals)
- if not val:
- flag = False
- pydev_log.debug("Condition '%s' is evaluated to %s. Not suspending.\n" % (django_breakpoint.condition, val))
- except:
- pydev_log.info(
- 'Error while evaluating condition \'%s\': %s\n' % (django_breakpoint.condition, sys.exc_info()[1]))
-
- if django_breakpoint.expression is not None:
- try:
- try:
- val = eval(django_breakpoint.expression, new_frame.f_globals, new_frame.f_locals)
- except:
- val = sys.exc_info()[1]
- finally:
- if val is not None:
- thread.additionalInfo.message = val
- if flag:
- frame = suspend_django(self, mainDebugger, thread, frame)
- return flag, frame
-
-def add_exception_to_frame(frame, exception_info):
- frame.f_locals['__exception__'] = exception_info \ No newline at end of file
+ pass #ok, psyco not available \ No newline at end of file
diff --git a/python/helpers/pydev/pydevd_frame_utils.py b/python/helpers/pydev/pydevd_frame_utils.py
index 23becca0570d..0c9e8446d19c 100644
--- a/python/helpers/pydev/pydevd_frame_utils.py
+++ b/python/helpers/pydev/pydevd_frame_utils.py
@@ -1,11 +1,11 @@
-class Frame:
+class Frame(object):
def __init__(
self,
f_back,
f_fileno,
f_code,
f_locals,
- f_globals={},
+ f_globals=None,
f_trace=None):
self.f_back = f_back
self.f_lineno = f_fileno
@@ -14,8 +14,31 @@ class Frame:
self.f_globals = f_globals
self.f_trace = f_trace
+ if self.f_globals is None:
+ self.f_globals = {}
-class FCode:
+
+class FCode(object):
def __init__(self, name, filename):
self.co_name = name
- self.co_filename = filename \ No newline at end of file
+ self.co_filename = filename
+
+
+def add_exception_to_frame(frame, exception_info):
+ frame.f_locals['__exception__'] = exception_info
+
+
+def just_raised(trace):
+ if trace is None:
+ return False
+ return trace.tb_next is None
+
+
+def cached_call(obj, func, *args):
+ cached_name = '_cached_' + func.__name__
+ if not hasattr(obj, cached_name):
+ setattr(obj, cached_name, func(*args))
+
+ return getattr(obj, cached_name)
+
+
diff --git a/python/helpers/pydev/pydevd_plugin_utils.py b/python/helpers/pydev/pydevd_plugin_utils.py
new file mode 100644
index 000000000000..5b106b8234db
--- /dev/null
+++ b/python/helpers/pydev/pydevd_plugin_utils.py
@@ -0,0 +1,85 @@
+import os
+import types
+
+import pydev_log
+import pydevd_trace_api
+from third_party.pluginbase import PluginBase
+
+def load_plugins(package):
+ plugin_base = PluginBase(package=package)
+ plugin_source = plugin_base.make_plugin_source(searchpath=[os.path.dirname(os.path.realpath(__file__)) + '/' + package], persist=True)
+ plugins = []
+ for plugin in plugin_source.list_plugins():
+ loaded_plugin = None
+ try:
+ loaded_plugin = plugin_source.load_plugin(plugin)
+ except:
+ pydev_log.error("Failed to load plugin %s" % plugin, True)
+ if loaded_plugin:
+ plugins.append(loaded_plugin)
+
+ return plugins
+
+
+def bind_func_to_method(func, obj, method_name):
+ foo = types.MethodType(func, obj)
+
+ setattr(obj, method_name, foo)
+ return foo
+
+
+class PluginManager(object):
+ def __init__(self, main_debugger):
+ self.plugins = load_plugins('pydevd_plugins')
+ self.active_plugins = []
+ self.main_debugger = main_debugger
+ self.rebind_methods()
+
+ def add_breakpoint(self, func_name, *args, **kwargs):
+ # add breakpoint for plugin and remember which plugin to use in tracing
+ for plugin in self.plugins:
+ if hasattr(plugin, func_name):
+ func = getattr(plugin, func_name)
+ result = func(self, *args, **kwargs)
+ if result:
+ self.activate(plugin)
+
+ return result
+ return None
+
+ def activate(self, plugin):
+ self.active_plugins.append(plugin)
+ self.rebind_methods()
+
+ def rebind_methods(self):
+ if len(self.active_plugins) == 0:
+ self.bind_functions(pydevd_trace_api, getattr, pydevd_trace_api)
+ elif len(self.active_plugins) == 1:
+ self.bind_functions(pydevd_trace_api, getattr, self.active_plugins[0])
+ else:
+ self.bind_functions(pydevd_trace_api, create_dispatch, self.active_plugins)
+
+ def bind_functions(self, interface, function_factory, arg):
+ for name in dir(interface):
+ func = function_factory(arg, name)
+ if type(func) == types.FunctionType:
+ bind_func_to_method(func, self, name)
+
+
+def create_dispatch(obj, name):
+ def dispatch(self, *args, **kwargs):
+ result = None
+ for p in self.active_plugins:
+ r = getattr(p, name)(self, *args, **kwargs)
+ if not result:
+ result = r
+ return result
+ return dispatch
+
+
+
+
+
+
+
+
diff --git a/python/helpers/pydev/pydevd_plugins/__init__.py b/python/helpers/pydev/pydevd_plugins/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/python/helpers/pydev/pydevd_plugins/__init__.py
diff --git a/python/helpers/pydev/pydevd_plugins/django_debug.py b/python/helpers/pydev/pydevd_plugins/django_debug.py
new file mode 100644
index 000000000000..ac23d40bd381
--- /dev/null
+++ b/python/helpers/pydev/pydevd_plugins/django_debug.py
@@ -0,0 +1,357 @@
+from pydevd_comm import CMD_SET_BREAK, CMD_ADD_EXCEPTION_BREAK
+import inspect
+from pydevd_constants import STATE_SUSPEND, GetThreadId, DictContains
+from pydevd_file_utils import NormFileToServer, GetFileNameAndBaseFromFile
+from pydevd_breakpoints import LineBreakpoint, get_exception_name
+import pydevd_vars
+import traceback
+import pydev_log
+from pydevd_frame_utils import add_exception_to_frame, FCode, cached_call, just_raised
+
+DJANGO_SUSPEND = 2
+
+class DjangoLineBreakpoint(LineBreakpoint):
+ def __init__(self, file, line, condition, func_name, expression):
+ self.file = file
+ LineBreakpoint.__init__(self, line, condition, func_name, expression)
+
+ def is_triggered(self, template_frame_file, template_frame_line):
+ return self.file == template_frame_file and self.line == template_frame_line
+
+ def __str__(self):
+ return "DjangoLineBreakpoint: %s-%d" %(self.file, self.line)
+
+
+def add_line_breakpoint(plugin, pydb, type, file, line, condition, expression, func_name):
+ if type == 'django-line':
+ breakpoint = DjangoLineBreakpoint(file, line, condition, func_name, expression)
+ if not hasattr(pydb, 'django_breakpoints'):
+ pydb.django_breakpoints = {}
+ return breakpoint, pydb.django_breakpoints
+ return None
+
+def add_exception_breakpoint(plugin, pydb, type, exception):
+ if type == 'django':
+ if not hasattr(pydb, 'django_exception_break'):
+ pydb.django_exception_break = {}
+ pydb.django_exception_break[exception] = True
+ pydb.setTracingForUntracedContexts()
+ return True
+ return False
+
+
+def remove_exception_breakpoint(plugin, pydb, type, exception):
+ if type == 'django':
+ try:
+ del pydb.django_exception_break[exception]
+ return True
+ except:
+ pass
+ return False
+
+def get_breakpoints(plugin, pydb, type):
+ if type == 'django-line':
+ return pydb.django_breakpoints
+ return None
+
+def _inherits(cls, *names):
+ if cls.__name__ in names:
+ return True
+ inherits_node = False
+ for base in inspect.getmro(cls):
+ if base.__name__ in names:
+ inherits_node = True
+ break
+ return inherits_node
+
+
+def _is_django_render_call(frame):
+ try:
+ name = frame.f_code.co_name
+ if name != 'render':
+ return False
+
+ if not DictContains(frame.f_locals, 'self'):
+ return False
+
+ cls = frame.f_locals['self'].__class__
+
+ inherits_node = _inherits(cls, 'Node')
+
+ if not inherits_node:
+ return False
+
+ clsname = cls.__name__
+ return clsname != 'TextNode' and clsname != 'NodeList'
+ except:
+ traceback.print_exc()
+ return False
+
+
+def _is_django_context_get_call(frame):
+ try:
+ if not DictContains(frame.f_locals, 'self'):
+ return False
+
+ cls = frame.f_locals['self'].__class__
+
+ return _inherits(cls, 'BaseContext')
+ except:
+ traceback.print_exc()
+ return False
+
+
+def _is_django_resolve_call(frame):
+ try:
+ name = frame.f_code.co_name
+ if name != '_resolve_lookup':
+ return False
+
+ if not DictContains(frame.f_locals, 'self'):
+ return False
+
+ cls = frame.f_locals['self'].__class__
+
+ clsname = cls.__name__
+ return clsname == 'Variable'
+ except:
+ traceback.print_exc()
+ return False
+
+
+def _is_django_suspended(thread):
+ return thread.additionalInfo.suspend_type == DJANGO_SUSPEND
+
+
+def suspend_django(mainDebugger, thread, frame, cmd=CMD_SET_BREAK):
+ frame = DjangoTemplateFrame(frame)
+
+ if frame.f_lineno is None:
+ return None
+
+ #try:
+ # if thread.additionalInfo.filename == frame.f_code.co_filename and thread.additionalInfo.line == frame.f_lineno:
+ # return None # don't stay twice on the same line
+ #except AttributeError:
+ # pass
+
+ pydevd_vars.addAdditionalFrameById(GetThreadId(thread), {id(frame): frame})
+
+ mainDebugger.setSuspend(thread, cmd)
+ thread.additionalInfo.suspend_type = DJANGO_SUSPEND
+
+ thread.additionalInfo.filename = frame.f_code.co_filename
+ thread.additionalInfo.line = frame.f_lineno
+
+ return frame
+
+
+def _find_django_render_frame(frame):
+ while frame is not None and not _is_django_render_call(frame):
+ frame = frame.f_back
+
+ return frame
+
+#=======================================================================================================================
+# Django Frame
+#=======================================================================================================================
+
+def _read_file(filename):
+ f = open(filename, "r")
+ s = f.read()
+ f.close()
+ return s
+
+
+def _offset_to_line_number(text, offset):
+ curLine = 1
+ curOffset = 0
+ while curOffset < offset:
+ if curOffset == len(text):
+ return -1
+ c = text[curOffset]
+ if c == '\n':
+ curLine += 1
+ elif c == '\r':
+ curLine += 1
+ if curOffset < len(text) and text[curOffset + 1] == '\n':
+ curOffset += 1
+
+ curOffset += 1
+
+ return curLine
+
+
+def _get_source(frame):
+ try:
+ node = frame.f_locals['self']
+ if hasattr(node, 'source'):
+ return node.source
+ else:
+ pydev_log.error_once("WARNING: Template path is not available. Please set TEMPLATE_DEBUG=True in your settings.py to make "
+ " django template breakpoints working")
+ return None
+
+ except:
+ pydev_log.debug(traceback.format_exc())
+ return None
+
+
+def _get_template_file_name(frame):
+ try:
+ source = _get_source(frame)
+ if source is None:
+ pydev_log.debug("Source is None\n")
+ return None
+ fname = source[0].name
+
+ if fname == '<unknown source>':
+ pydev_log.debug("Source name is %s\n" % fname)
+ return None
+ else:
+ filename, base = GetFileNameAndBaseFromFile(fname)
+ return filename
+ except:
+ pydev_log.debug(traceback.format_exc())
+ return None
+
+
+def _get_template_line(frame):
+ source = _get_source(frame)
+ file_name = _get_template_file_name(frame)
+ try:
+ return _offset_to_line_number(_read_file(file_name), source[1][0])
+ except:
+ return None
+
+
+class DjangoTemplateFrame:
+ def __init__(self, frame):
+ file_name = _get_template_file_name(frame)
+ self.back_context = frame.f_locals['context']
+ self.f_code = FCode('Django Template', file_name)
+ self.f_lineno = _get_template_line(frame)
+ self.f_back = frame
+ self.f_globals = {}
+ self.f_locals = self.collect_context(self.back_context)
+ self.f_trace = None
+
+ def collect_context(self, context):
+ res = {}
+ try:
+ for d in context.dicts:
+ for k, v in d.items():
+ res[k] = v
+ except AttributeError:
+ pass
+ return res
+
+ def changeVariable(self, name, value):
+ for d in self.back_context.dicts:
+ for k, v in d.items():
+ if k == name:
+ d[k] = value
+
+def _is_django_exception_break_context(frame):
+ try:
+ name = frame.f_code.co_name
+ except:
+ name = None
+ return name in ['_resolve_lookup', 'find_template']
+
+
+#=======================================================================================================================
+# Django Step Commands
+#=======================================================================================================================
+
+def can_not_skip(plugin, mainDebugger, pydb_frame, frame):
+ if hasattr(mainDebugger, 'django_breakpoints') and mainDebugger.django_breakpoints and cached_call(pydb_frame, _is_django_render_call, frame):
+ filename = _get_template_file_name(frame)
+ django_breakpoints_for_file = mainDebugger.django_breakpoints.get(filename)
+ if django_breakpoints_for_file:
+ return True
+ return False
+
+def has_exception_breaks(plugin, mainDebugger):
+ return hasattr(mainDebugger, 'django_exception_break') and mainDebugger.django_exception_break
+
+
+def cmd_step_into(plugin, mainDebugger, frame, event, args, stop_info):
+ mainDebugger, filename, info, thread = args
+ if _is_django_suspended(thread):
+ #stop_info['django_stop'] = event == 'call' and cached_call(frame, is_django_render_call)
+ stop_info['stop'] = stop_info['stop'] and _is_django_resolve_call(frame.f_back) and not _is_django_context_get_call(frame)
+ if stop_info['stop']:
+ info.pydev_django_resolve_frame = 1 #we remember that we've go into python code from django rendering frame
+
+
+def cmd_step_over(plugin, mainDebugger, frame, event, args, stop_info):
+ mainDebugger, filename, info, thread = args
+ if _is_django_suspended(thread):
+ stop_info['django_stop'] = event == 'call' and _is_django_render_call(frame)
+ stop_info['stop'] = False
+ return True
+ else:
+ if event == 'return' and info.pydev_django_resolve_frame is not None and _is_django_resolve_call(frame.f_back):
+ #we return to Django suspend mode and should not stop before django rendering frame
+ info.pydev_step_stop = info.pydev_django_resolve_frame
+ info.pydev_django_resolve_frame = None
+ thread.additionalInfo.suspend_type = DJANGO_SUSPEND
+ stop_info['stop'] = info.pydev_step_stop is frame and event in ('line', 'return')
+
+ return False
+
+
+def stop(plugin, mainDebugger, frame, event, args, stop_info, arg, step_cmd):
+ mainDebugger, filename, info, thread = args
+ if DictContains(stop_info, 'django_stop') and stop_info['django_stop']:
+ frame = suspend_django(mainDebugger, thread, frame, step_cmd)
+ if frame:
+ mainDebugger.doWaitSuspend(thread, frame, event, arg)
+ return True
+ return False
+
+
+def get_breakpoint(plugin, mainDebugger, pydb_frame, frame, event, args):
+ mainDebugger, filename, info, thread = args
+ flag = False
+ django_breakpoint = None
+ new_frame = None
+
+ if event == 'call' and info.pydev_state != STATE_SUSPEND and hasattr(mainDebugger, 'django_breakpoints') and \
+ mainDebugger.django_breakpoints and cached_call(pydb_frame, _is_django_render_call, frame):
+ filename = _get_template_file_name(frame)
+ pydev_log.debug("Django is rendering a template: %s\n" % filename)
+ django_breakpoints_for_file = mainDebugger.django_breakpoints.get(filename)
+ if django_breakpoints_for_file:
+ pydev_log.debug("Breakpoints for that file: %s\n" % django_breakpoints_for_file)
+ template_line = _get_template_line(frame)
+ pydev_log.debug("Tracing template line: %d\n" % template_line)
+
+ if DictContains(django_breakpoints_for_file, template_line):
+ django_breakpoint = django_breakpoints_for_file[template_line]
+ flag = True
+ new_frame = DjangoTemplateFrame(frame)
+ return flag, django_breakpoint, new_frame
+
+
+def suspend(plugin, mainDebugger, thread, frame):
+ return suspend_django(mainDebugger, thread, frame)
+
+def exception_break(plugin, mainDebugger, pydb_frame, frame, args, arg):
+ mainDebugger, filename, info, thread = args
+ exception, value, trace = arg
+ if hasattr(mainDebugger, 'django_exception_break') and mainDebugger.django_exception_break and \
+ get_exception_name(exception) in ['VariableDoesNotExist', 'TemplateDoesNotExist', 'TemplateSyntaxError'] and \
+ just_raised(trace) and _is_django_exception_break_context(frame):
+ render_frame = _find_django_render_frame(frame)
+ if render_frame:
+ suspend_frame = suspend_django(mainDebugger, thread, render_frame, CMD_ADD_EXCEPTION_BREAK)
+ if suspend_frame:
+ add_exception_to_frame(suspend_frame, (exception, value, trace))
+ flag = True
+ thread.additionalInfo.message = 'VariableDoesNotExist'
+ suspend_frame.f_back = frame
+ frame = suspend_frame
+ return (flag, frame)
+ return None \ No newline at end of file
diff --git a/python/helpers/pydev/pydevd_plugins/jinja2_debug.py b/python/helpers/pydev/pydevd_plugins/jinja2_debug.py
new file mode 100644
index 000000000000..9968a81d0043
--- /dev/null
+++ b/python/helpers/pydev/pydevd_plugins/jinja2_debug.py
@@ -0,0 +1,341 @@
+import traceback
+from pydevd_breakpoints import LineBreakpoint, get_exception_name
+from pydevd_constants import GetThreadId, STATE_SUSPEND, DictContains
+from pydevd_comm import CMD_SET_BREAK, CMD_STEP_OVER, CMD_ADD_EXCEPTION_BREAK
+import pydevd_vars
+from pydevd_file_utils import GetFileNameAndBaseFromFile
+from pydevd_frame_utils import add_exception_to_frame, FCode, cached_call
+
+JINJA2_SUSPEND = 3
+
+class Jinja2LineBreakpoint(LineBreakpoint):
+
+ def __init__(self, file, line, condition, func_name, expression):
+ self.file = file
+ LineBreakpoint.__init__(self, line, condition, func_name, expression)
+
+ def is_triggered(self, template_frame_file, template_frame_line):
+ return self.file == template_frame_file and self.line == template_frame_line
+
+ def __str__(self):
+ return "Jinja2LineBreakpoint: %s-%d" %(self.file, self.line)
+
+
+def add_line_breakpoint(plugin, pydb, type, file, line, condition, func_name, expression):
+ result = None
+ if type == 'jinja2-line':
+ breakpoint = Jinja2LineBreakpoint(file, line, condition, func_name, expression)
+ if not hasattr(pydb, 'jinja2_breakpoints'):
+ pydb.jinja2_breakpoints = {}
+ result = breakpoint, pydb.jinja2_breakpoints
+ return result
+ return result
+
+def add_exception_breakpoint(plugin, pydb, type, exception):
+ if type == 'jinja2':
+ if not hasattr(pydb, 'jinja2_exception_break'):
+ pydb.jinja2_exception_break = {}
+ pydb.jinja2_exception_break[exception] = True
+ pydb.setTracingForUntracedContexts()
+ return True
+ return False
+
+def remove_exception_breakpoint(plugin, pydb, type, exception):
+ if type == 'jinja2':
+ try:
+ del pydb.jinja2_exception_break[exception]
+ return True
+ except:
+ pass
+ return False
+
+def get_breakpoints(plugin, pydb, type):
+ if type == 'jinja2-line':
+ return pydb.jinja2_breakpoints
+ return None
+
+
+def is_jinja2_render_call(frame):
+ try:
+ name = frame.f_code.co_name
+ if DictContains(frame.f_globals, "__jinja_template__") and name in ("root", "loop", "macro") or name.startswith("block_"):
+ return True
+ return False
+ except:
+ traceback.print_exc()
+ return False
+
+
+def suspend_jinja2(pydb, thread, frame, cmd=CMD_SET_BREAK):
+ frame = Jinja2TemplateFrame(frame)
+
+ if frame.f_lineno is None:
+ return None
+
+ pydevd_vars.addAdditionalFrameById(GetThreadId(thread), {id(frame): frame})
+ pydb.setSuspend(thread, cmd)
+
+ thread.additionalInfo.suspend_type = JINJA2_SUSPEND
+ thread.additionalInfo.filename = frame.f_code.co_filename
+ thread.additionalInfo.line = frame.f_lineno
+
+ return frame
+
+def is_jinja2_suspended(thread):
+ return thread.additionalInfo.suspend_type == JINJA2_SUSPEND
+
+def is_jinja2_context_call(frame):
+ return DictContains(frame.f_locals, "_Context__obj")
+
+def is_jinja2_internal_function(frame):
+ return DictContains(frame.f_locals, 'self') and frame.f_locals['self'].__class__.__name__ in \
+ ('LoopContext', 'TemplateReference', 'Macro', 'BlockReference')
+
+def find_jinja2_render_frame(frame):
+ while frame is not None and not is_jinja2_render_call(frame):
+ frame = frame.f_back
+
+ return frame
+
+def change_variable(plugin, pydb, frame, attr, expression):
+ if isinstance(frame, Jinja2TemplateFrame):
+ result = eval(expression, frame.f_globals, frame.f_locals)
+ frame.changeVariable(attr, result)
+
+
+#=======================================================================================================================
+# Jinja2 Frame
+#=======================================================================================================================
+
+class Jinja2TemplateFrame:
+
+ def __init__(self, frame):
+ file_name = get_jinja2_template_filename(frame)
+ self.back_context = None
+ if 'context' in frame.f_locals:
+ #sometimes we don't have 'context', e.g. in macros
+ self.back_context = frame.f_locals['context']
+ self.f_code = FCode('template', file_name)
+ self.f_lineno = get_jinja2_template_line(frame)
+ self.f_back = find_render_function_frame(frame)
+ self.f_globals = {}
+ self.f_locals = self.collect_context(frame)
+ self.f_trace = None
+
+ def collect_context(self, frame):
+ res = {}
+ if self.back_context is not None:
+ for k, v in self.back_context.items():
+ res[k] = v
+ for k, v in frame.f_locals.items():
+ if not k.startswith('l_'):
+ if not k in res:
+ #local variables should shadow globals from context
+ res[k] = v
+ elif v and not is_missing(v):
+ res[k[2:]] = v
+ return res
+
+ def changeVariable(self, name, value):
+ for k, v in self.back_context.items():
+ if k == name:
+ self.back_context.vars[k] = value
+
+def is_missing(item):
+ if item.__class__.__name__ == 'MissingType':
+ return True
+ return False
+
+def find_render_function_frame(frame):
+ #in order to hide internal rendering functions
+ old_frame = frame
+ try:
+ while not (DictContains(frame.f_locals, 'self') and frame.f_locals['self'].__class__.__name__ == 'Template' and \
+ frame.f_code.co_name == 'render'):
+ frame = frame.f_back
+ if frame is None:
+ return old_frame
+ return frame
+ except:
+ return old_frame
+
+def get_jinja2_template_line(frame):
+ debug_info = None
+ if DictContains(frame.f_globals,'__jinja_template__'):
+ _debug_info = frame.f_globals['__jinja_template__']._debug_info
+ if _debug_info != '':
+ #sometimes template contains only plain text
+ debug_info = frame.f_globals['__jinja_template__'].debug_info
+
+ if debug_info is None:
+ return None
+
+ lineno = frame.f_lineno
+
+ for pair in debug_info:
+ if pair[1] == lineno:
+ return pair[0]
+
+ return None
+
+def get_jinja2_template_filename(frame):
+ if DictContains(frame.f_globals, '__jinja_template__'):
+ fname = frame.f_globals['__jinja_template__'].filename
+ filename, base = GetFileNameAndBaseFromFile(fname)
+ return filename
+ return None
+
+
+#=======================================================================================================================
+# Jinja2 Step Commands
+#=======================================================================================================================
+
+
+def has_exception_breaks(plugin, pydb):
+ return hasattr(pydb, 'jinja2_exception_break') and pydb.jinja2_exception_break
+
+def can_not_skip(plugin, pydb, pydb_frame, frame):
+ if hasattr(pydb, 'jinja2_breakpoints') and pydb.jinja2_breakpoints and cached_call(pydb_frame, is_jinja2_render_call, frame):
+ filename = get_jinja2_template_filename(frame)
+ jinja2_breakpoints_for_file = pydb.jinja2_breakpoints.get(filename)
+ if jinja2_breakpoints_for_file:
+ return True
+ return False
+
+
+def cmd_step_into(plugin, pydb, frame, event, args, stop_info):
+ pydb, filename, info, thread = args
+ if not hasattr(info, 'pydev_call_from_jinja2'):
+ info.pydev_call_from_jinja2 = None
+ if not hasattr(info, 'pydev_call_inside_jinja2'):
+ info.pydev_call_inside_jinja2 = None
+ if is_jinja2_suspended(thread):
+ stop_info['jinja2_stop'] = event in ('call', 'line') and is_jinja2_render_call(frame)
+ stop_info['stop'] = False
+ if info.pydev_call_from_jinja2 is not None:
+ if is_jinja2_internal_function(frame):
+ #if internal Jinja2 function was called, we sould continue debugging inside template
+ info.pydev_call_from_jinja2 = None
+ else:
+ #we go into python code from Jinja2 rendering frame
+ stop_info['stop'] = True
+
+ if event == 'call' and is_jinja2_context_call(frame.f_back):
+ #we called function from context, the next step will be in function
+ info.pydev_call_from_jinja2 = 1
+
+ if event == 'return' and is_jinja2_context_call(frame.f_back):
+ #we return from python code to Jinja2 rendering frame
+ info.pydev_step_stop = info.pydev_call_from_jinja2
+ info.pydev_call_from_jinja2 = None
+ thread.additionalInfo.suspend_type = JINJA2_SUSPEND
+ stop_info['stop'] = False
+
+ #print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop_info", stop_info, \
+ # "thread.additionalInfo.suspend_type", thread.additionalInfo.suspend_type
+ #print "event", event, "farme.locals", frame.f_locals
+
+
+def cmd_step_over(plugin, pydb, frame, event, args, stop_info):
+ pydb, filename, info, thread = args
+ if not hasattr(info, 'pydev_call_from_jinja2'):
+ info.pydev_call_from_jinja2 = None
+ if not hasattr(info, 'pydev_call_inside_jinja2'):
+ info.pydev_call_inside_jinja2 = None
+ if is_jinja2_suspended(thread):
+ stop_info['stop'] = False
+
+ if info.pydev_call_inside_jinja2 is None:
+ if is_jinja2_render_call(frame):
+ if event == 'call':
+ info.pydev_call_inside_jinja2 = frame.f_back
+ if event in ('line', 'return'):
+ info.pydev_call_inside_jinja2 = frame
+ else:
+ if event == 'line':
+ if is_jinja2_render_call(frame) and info.pydev_call_inside_jinja2 is frame:
+ stop_info['jinja2_stop'] = True
+ if event == 'return':
+ if frame is info.pydev_call_inside_jinja2 and not DictContains(frame.f_back.f_locals,'event'):
+ info.pydev_call_inside_jinja2 = find_jinja2_render_frame(frame.f_back)
+ return True
+ else:
+ if event == 'return' and is_jinja2_context_call(frame.f_back):
+ #we return from python code to Jinja2 rendering frame
+ info.pydev_call_from_jinja2 = None
+ info.pydev_call_inside_jinja2 = find_jinja2_render_frame(frame)
+ thread.additionalInfo.suspend_type = JINJA2_SUSPEND
+ stop_info['stop'] = False
+ return True
+ #print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop", stop, "jinja_stop", jinja2_stop, \
+ # "thread.additionalInfo.suspend_type", thread.additionalInfo.suspend_type
+ #print "event", event, "info.pydev_call_inside_jinja2", info.pydev_call_inside_jinja2
+ #print "frame", frame, "frame.f_back", frame.f_back, "step_stop", info.pydev_step_stop
+ #print "is_context_call", is_jinja2_context_call(frame)
+ #print "render", is_jinja2_render_call(frame)
+ #print "-------------"
+ return False
+
+
+def stop(plugin, pydb, frame, event, args, stop_info, arg, step_cmd):
+ pydb, filename, info, thread = args
+ if DictContains(stop_info, 'jinja2_stop') and stop_info['jinja2_stop']:
+ frame = suspend_jinja2(pydb, thread, frame, step_cmd)
+ if frame:
+ pydb.doWaitSuspend(thread, frame, event, arg)
+ return True
+ return False
+
+
+def get_breakpoint(plugin, pydb, pydb_frame, frame, event, args):
+ pydb, filename, info, thread = args
+ new_frame = None
+ jinja2_breakpoint = None
+ flag = False
+ if event in ('line', 'call') and info.pydev_state != STATE_SUSPEND and hasattr(pydb, 'jinja2_breakpoints') and \
+ pydb.jinja2_breakpoints and cached_call(pydb_frame, is_jinja2_render_call, frame):
+ filename = get_jinja2_template_filename(frame)
+ jinja2_breakpoints_for_file = pydb.jinja2_breakpoints.get(filename)
+ new_frame = Jinja2TemplateFrame(frame)
+
+ if jinja2_breakpoints_for_file:
+ lineno = frame.f_lineno
+ template_lineno = get_jinja2_template_line(frame)
+ if template_lineno is not None and DictContains(jinja2_breakpoints_for_file, template_lineno):
+ jinja2_breakpoint = jinja2_breakpoints_for_file[template_lineno]
+ flag = True
+ new_frame = Jinja2TemplateFrame(frame)
+
+ return flag, jinja2_breakpoint, new_frame
+
+
+def suspend(plugin, pydb, thread, frame):
+ return suspend_jinja2(pydb, thread, frame)
+
+
+def exception_break(plugin, pydb, pydb_frame, frame, args, arg):
+ pydb, filename, info, thread = args
+ exception, value, trace = arg
+ if hasattr(pydb, 'jinja2_exception_break') and pydb.jinja2_exception_break:
+ if get_exception_name(exception) in ('UndefinedError', 'TemplateNotFound', 'TemplatesNotFound'):
+ #errors in rendering
+ render_frame = find_jinja2_render_frame(frame)
+ if render_frame:
+ suspend_frame = suspend_jinja2(pydb, thread, render_frame, CMD_ADD_EXCEPTION_BREAK)
+ if suspend_frame:
+ add_exception_to_frame(suspend_frame, (exception, value, trace))
+ flag = True
+ suspend_frame.f_back = frame
+ frame = suspend_frame
+ return flag, frame
+ elif get_exception_name(exception) in ('TemplateSyntaxError', 'TemplateAssertionError'):
+ #errors in compile time
+ name = frame.f_code.co_name
+ if name in ('template', 'top-level template code') or name.startswith('block '):
+ #Jinja2 translates exception info and creates fake frame on his own
+ pydb_frame.setSuspend(thread, CMD_ADD_EXCEPTION_BREAK)
+ add_exception_to_frame(frame, (exception, value, trace))
+ thread.additionalInfo.suspend_type = JINJA2_SUSPEND
+ flag = True
+ return flag, frame
+ return None \ No newline at end of file
diff --git a/python/helpers/pydev/pydevd_resolver.py b/python/helpers/pydev/pydevd_resolver.py
index ad49bd881ba0..444dead4cce7 100644
--- a/python/helpers/pydev/pydevd_resolver.py
+++ b/python/helpers/pydev/pydevd_resolver.py
@@ -409,6 +409,17 @@ class NdArrayResolver:
return obj.dtype
if attribute == 'size':
return obj.size
+ if attribute.startswith('['):
+ container = NdArrayItemsContainer()
+ i = 0
+ format_str = '%0' + str(int(len(str(len(obj))))) + 'd'
+ for item in obj:
+ setattr(container, format_str % i, item)
+ i += 1
+ if i > MAX_ITEMS_TO_HANDLE:
+ setattr(container, TOO_LARGE_ATTR, TOO_LARGE_MSG)
+ break
+ return container
return None
def getDictionary(self, obj):
@@ -427,9 +438,10 @@ class NdArrayResolver:
ret['shape'] = obj.shape
ret['dtype'] = obj.dtype
ret['size'] = obj.size
+ ret['[0:%s]' % (len(obj))] = list(obj)
return ret
-
+class NdArrayItemsContainer: pass
#=======================================================================================================================
# FrameResolver
#=======================================================================================================================
diff --git a/python/helpers/pydev/pydevd_trace_api.py b/python/helpers/pydev/pydevd_trace_api.py
new file mode 100644
index 000000000000..5d2f30e98a3a
--- /dev/null
+++ b/python/helpers/pydev/pydevd_trace_api.py
@@ -0,0 +1,35 @@
+def add_line_breakpoint(plugin, pydb, type, file, line, condition, expression, func_name):
+ return None
+
+def add_exception_breakpoint(plugin, pydb, type, exception):
+ return False
+
+def remove_exception_breakpoint(plugin, pydb, type, exception):
+ return False
+
+def get_breakpoints(plugin, pydb):
+ return None
+
+def can_not_skip(plugin, pydb, pydb_frame, frame):
+ return False
+
+def has_exception_breaks(plugin, pydb):
+ return False
+
+def cmd_step_into(plugin, pydb, frame, event, args, stop_info):
+ return False
+
+def cmd_step_over(plugin, pydb, frame, event, args, stop_info):
+ return False
+
+def stop(plugin, pydb, frame, event, args, stop_info, arg, step_cmd):
+ return False
+
+def get_breakpoint(plugin, pydb, pydb_frame, frame, event, args):
+ return None
+
+def suspend(plugin, pydb, thread, frame):
+ return None
+
+def exception_break(plugin, pydb, pydb_frame, frame, args, arg):
+ return None \ No newline at end of file
diff --git a/python/helpers/pydev/pydevd_utils.py b/python/helpers/pydev/pydevd_utils.py
index 134b190881ac..753263ba4f31 100644
--- a/python/helpers/pydev/pydevd_utils.py
+++ b/python/helpers/pydev/pydevd_utils.py
@@ -6,7 +6,28 @@ except:
from urllib.parse import quote
import pydevd_constants
-import pydev_log
+import sys
+
+
+def save_main_module(file, module_name):
+ # patch provided by: Scott Schlesier - when script is run, it does not
+ # use globals from pydevd:
+ # This will prevent the pydevd script from contaminating the namespace for the script to be debugged
+ # pretend pydevd is not the main module, and
+ # convince the file to be debugged that it was loaded as main
+ sys.modules[module_name] = sys.modules['__main__']
+ sys.modules[module_name].__name__ = module_name
+ from imp import new_module
+
+ m = new_module('__main__')
+ sys.modules['__main__'] = m
+ if hasattr(sys.modules[module_name], '__loader__'):
+ setattr(m, '__loader__',
+ getattr(sys.modules[module_name], '__loader__'))
+ m.__file__ = file
+
+ return m
+
def to_number(x):
if is_string(x):
diff --git a/python/helpers/pydev/pydevd_vars.py b/python/helpers/pydev/pydevd_vars.py
index 3baea5b61b99..e1aa436b8946 100644
--- a/python/helpers/pydev/pydevd_vars.py
+++ b/python/helpers/pydev/pydevd_vars.py
@@ -2,7 +2,6 @@
resolution/conversion to XML.
"""
import pickle
-from django_frame import DjangoTemplateFrame
from pydevd_constants import * #@UnusedWildImport
from types import * #@UnusedWildImport
@@ -360,10 +359,10 @@ def changeAttrExpression(thread_id, frame_id, attr, expression):
try:
expression = expression.replace('@LINE@', '\n')
- if isinstance(frame, DjangoTemplateFrame):
- result = eval(expression, frame.f_globals, frame.f_locals)
- frame.changeVariable(attr, result)
- return
+ # if isinstance(frame, DjangoTemplateFrame): # TODO: implemente for plugins
+ # result = eval(expression, frame.f_globals, frame.f_locals)
+ # frame.changeVariable(attr, result)
+ # return result
if attr[:7] == "Globals":
attr = attr[8:]
@@ -374,7 +373,7 @@ def changeAttrExpression(thread_id, frame_id, attr, expression):
if pydevd_save_locals.is_save_locals_available():
frame.f_locals[attr] = eval(expression, frame.f_globals, frame.f_locals)
pydevd_save_locals.save_locals(frame)
- return
+ return frame.f_locals[attr]
#default way (only works for changing it in the topmost frame)
result = eval(expression, frame.f_globals, frame.f_locals)
diff --git a/python/helpers/pydev/test_debug.py b/python/helpers/pydev/test_debug.py
index 2196ca6f9540..27da09b5593d 100644
--- a/python/helpers/pydev/test_debug.py
+++ b/python/helpers/pydev/test_debug.py
@@ -5,16 +5,18 @@ import os
test_data_path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', 'testData', 'debug'))
+
class PyDevTestCase(unittest.TestCase):
def testZipFileExits(self):
from pydevd_file_utils import exists
- self.assertTrue(exists(test_data_path +'/zipped_lib.zip/zipped_module.py'))
+ self.assertTrue(exists(test_data_path + '/zipped_lib.zip/zipped_module.py'))
self.assertFalse(exists(test_data_path + '/zipped_lib.zip/zipped_module2.py'))
self.assertFalse(exists(test_data_path + '/zipped_lib2.zip/zipped_module.py'))
def testEggFileExits(self):
from pydevd_file_utils import exists
+
self.assertTrue(exists(test_data_path + '/pycharm-debug.egg/pydev/pydevd.py'))
self.assertFalse(exists(test_data_path + '/pycharm-debug.egg/pydev/pydevd2.py'))
diff --git a/python/helpers/pydev/tests/check_pydevconsole.py b/python/helpers/pydev/tests/check_pydevconsole.py
new file mode 100644
index 000000000000..0ff1a8618a24
--- /dev/null
+++ b/python/helpers/pydev/tests/check_pydevconsole.py
@@ -0,0 +1,110 @@
+import sys
+import os
+
+# Put pydevconsole in the path.
+sys.argv[0] = os.path.dirname(sys.argv[0])
+sys.path.insert(1, os.path.join(os.path.dirname(sys.argv[0])))
+
+print('Running tests with:', sys.executable)
+print('PYTHONPATH:')
+print('\n'.join(sorted(sys.path)))
+
+import threading
+import unittest
+
+import pydevconsole
+from pydev_imports import xmlrpclib, SimpleXMLRPCServer
+
+try:
+ raw_input
+ raw_input_name = 'raw_input'
+except NameError:
+ raw_input_name = 'input'
+
+#=======================================================================================================================
+# Test
+#=======================================================================================================================
+class Test(unittest.TestCase):
+ def startClientThread(self, client_port):
+ class ClientThread(threading.Thread):
+ def __init__(self, client_port):
+ threading.Thread.__init__(self)
+ self.client_port = client_port
+
+ def run(self):
+ class HandleRequestInput:
+ def RequestInput(self):
+ return 'RequestInput: OK'
+
+ handle_request_input = HandleRequestInput()
+
+ import pydev_localhost
+
+ print('Starting client with:', pydev_localhost.get_localhost(), self.client_port)
+ client_server = SimpleXMLRPCServer((pydev_localhost.get_localhost(), self.client_port), logRequests=False)
+ client_server.register_function(handle_request_input.RequestInput)
+ client_server.serve_forever()
+
+ client_thread = ClientThread(client_port)
+ client_thread.setDaemon(True)
+ client_thread.start()
+ return client_thread
+
+
+ def getFreeAddresses(self):
+ import socket
+
+ s = socket.socket()
+ s.bind(('', 0))
+ port0 = s.getsockname()[1]
+
+ s1 = socket.socket()
+ s1.bind(('', 0))
+ port1 = s1.getsockname()[1]
+ s.close()
+ s1.close()
+ return port0, port1
+
+
+ def testServer(self):
+ client_port, server_port = self.getFreeAddresses()
+
+ class ServerThread(threading.Thread):
+ def __init__(self, client_port, server_port):
+ threading.Thread.__init__(self)
+ self.client_port = client_port
+ self.server_port = server_port
+
+ def run(self):
+ import pydev_localhost
+
+ print('Starting server with:', pydev_localhost.get_localhost(), self.server_port, self.client_port)
+ pydevconsole.StartServer(pydev_localhost.get_localhost(), self.server_port, self.client_port)
+
+ server_thread = ServerThread(client_port, server_port)
+ server_thread.setDaemon(True)
+ server_thread.start()
+
+ client_thread = self.startClientThread(client_port) #@UnusedVariable
+
+ import time
+
+ time.sleep(.3) #let's give it some time to start the threads
+
+ import pydev_localhost
+
+ server = xmlrpclib.Server('http://%s:%s' % (pydev_localhost.get_localhost(), server_port))
+ server.addExec("import sys; print('Running with: %s %s' % (sys.executable or sys.platform, sys.version))")
+ server.addExec('class Foo:')
+ server.addExec(' pass')
+ server.addExec('')
+ server.addExec('foo = Foo()')
+ server.addExec('a = %s()' % raw_input_name)
+ server.addExec('print (a)')
+
+#=======================================================================================================================
+# main
+#=======================================================================================================================
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/python/helpers/pydev/tests/test_pydev_ipython_010.py b/python/helpers/pydev/tests/test_pydev_ipython_010.py
new file mode 100644
index 000000000000..1822763d4b29
--- /dev/null
+++ b/python/helpers/pydev/tests/test_pydev_ipython_010.py
@@ -0,0 +1,80 @@
+# TODO: This test no longer works (check if it should be fixed or removed altogether).
+
+#import unittest
+#import sys
+#import os
+##make it as if we were executing from the directory above this one
+#sys.argv[0] = os.path.dirname(sys.argv[0])
+##twice the dirname to get the previous level from this file.
+#sys.path.insert(1, os.path.join(os.path.dirname(sys.argv[0])))
+#
+#from pydev_localhost import get_localhost
+#
+#
+#IS_JYTHON = sys.platform.find('java') != -1
+#
+##=======================================================================================================================
+## TestCase
+##=======================================================================================================================
+#class TestCase(unittest.TestCase):
+#
+# def setUp(self):
+# unittest.TestCase.setUp(self)
+#
+# def tearDown(self):
+# unittest.TestCase.tearDown(self)
+#
+# def testIPython(self):
+# try:
+# from pydev_ipython_console import PyDevFrontEnd
+# except:
+# if IS_JYTHON:
+# return
+# front_end = PyDevFrontEnd(get_localhost(), 0)
+#
+# front_end.input_buffer = 'if True:'
+# self.assert_(not front_end._on_enter())
+#
+# front_end.input_buffer = 'if True:\n' + \
+# front_end.continuation_prompt() + ' a = 10\n'
+# self.assert_(not front_end._on_enter())
+#
+#
+# front_end.input_buffer = 'if True:\n' + \
+# front_end.continuation_prompt() + ' a = 10\n\n'
+# self.assert_(front_end._on_enter())
+#
+#
+## front_end.input_buffer = ' print a'
+## self.assert_(not front_end._on_enter())
+## front_end.input_buffer = ''
+## self.assert_(front_end._on_enter())
+#
+#
+## front_end.input_buffer = 'a.'
+## front_end.complete_current_input()
+## front_end.input_buffer = 'if True:'
+## front_end._on_enter()
+# front_end.input_buffer = 'a = 30'
+# front_end._on_enter()
+# front_end.input_buffer = 'print a'
+# front_end._on_enter()
+# front_end.input_buffer = 'a?'
+# front_end._on_enter()
+# print front_end.complete('%')
+# print front_end.complete('%e')
+# print front_end.complete('cd c:/t')
+# print front_end.complete('cd c:/temp/')
+## front_end.input_buffer = 'print raw_input("press enter\\n")'
+## front_end._on_enter()
+##
+#
+##=======================================================================================================================
+## main
+##=======================================================================================================================
+#if __name__ == '__main__':
+# if sys.platform.find('java') == -1:
+# #IPython not available for Jython
+# unittest.main()
+# else:
+# print('not supported on Jython')
diff --git a/python/helpers/pydev/tests_python/_debugger_case_qthread3.py b/python/helpers/pydev/tests_python/_debugger_case_qthread3.py
index 22b0c91d7f13..9b326db7ccde 100644
--- a/python/helpers/pydev/tests_python/_debugger_case_qthread3.py
+++ b/python/helpers/pydev/tests_python/_debugger_case_qthread3.py
@@ -26,4 +26,5 @@ app = QtCore.QCoreApplication([])
runnable = Runnable()
QtCore.QThreadPool.globalInstance().start(runnable)
app.exec_()
+QtCore.QThreadPool.globalInstance().waitForDone()
print('TEST SUCEEDED!') \ No newline at end of file
diff --git a/python/helpers/pydev/third_party/pkgutil_old.py b/python/helpers/pydev/third_party/pkgutil_old.py
new file mode 100644
index 000000000000..ce072ec9ef75
--- /dev/null
+++ b/python/helpers/pydev/third_party/pkgutil_old.py
@@ -0,0 +1,591 @@
+"""Utilities to support packages."""
+
+# NOTE: This module must remain compatible with Python 2.3, as it is shared
+# by setuptools for distribution with Python 2.3 and up.
+
+import os
+import sys
+import imp
+import os.path
+from types import ModuleType
+
+__all__ = [
+ 'get_importer', 'iter_importers', 'get_loader', 'find_loader',
+ 'walk_packages', 'iter_modules', 'get_data',
+ 'ImpImporter', 'ImpLoader', 'read_code', 'extend_path',
+]
+
+def read_code(stream):
+ # This helper is needed in order for the PEP 302 emulation to
+ # correctly handle compiled files
+ import marshal
+
+ magic = stream.read(4)
+ if magic != imp.get_magic():
+ return None
+
+ stream.read(4) # Skip timestamp
+ return marshal.load(stream)
+
+
+def simplegeneric(func):
+ """Make a trivial single-dispatch generic function"""
+ registry = {}
+ def wrapper(*args, **kw):
+ ob = args[0]
+ try:
+ cls = ob.__class__
+ except AttributeError:
+ cls = type(ob)
+ try:
+ mro = cls.__mro__
+ except AttributeError:
+ try:
+ class cls(cls, object):
+ pass
+ mro = cls.__mro__[1:]
+ except TypeError:
+ mro = object, # must be an ExtensionClass or some such :(
+ for t in mro:
+ if t in registry:
+ return registry[t](*args, **kw)
+ else:
+ return func(*args, **kw)
+ try:
+ wrapper.__name__ = func.__name__
+ except (TypeError, AttributeError):
+ pass # Python 2.3 doesn't allow functions to be renamed
+
+ def register(typ, func=None):
+ if func is None:
+ return lambda f: register(typ, f)
+ registry[typ] = func
+ return func
+
+ wrapper.__dict__ = func.__dict__
+ wrapper.__doc__ = func.__doc__
+ wrapper.register = register
+ return wrapper
+
+
+def walk_packages(path=None, prefix='', onerror=None):
+ """Yields (module_loader, name, ispkg) for all modules recursively
+ on path, or, if path is None, all accessible modules.
+
+ 'path' should be either None or a list of paths to look for
+ modules in.
+
+ 'prefix' is a string to output on the front of every module name
+ on output.
+
+ Note that this function must import all *packages* (NOT all
+ modules!) on the given path, in order to access the __path__
+ attribute to find submodules.
+
+ 'onerror' is a function which gets called with one argument (the
+ name of the package which was being imported) if any exception
+ occurs while trying to import a package. If no onerror function is
+ supplied, ImportErrors are caught and ignored, while all other
+ exceptions are propagated, terminating the search.
+
+ Examples:
+
+ # list all modules python can access
+ walk_packages()
+
+ # list all submodules of ctypes
+ walk_packages(ctypes.__path__, ctypes.__name__+'.')
+ """
+
+ def seen(p, m={}):
+ if p in m:
+ return True
+ m[p] = True
+
+ for importer, name, ispkg in iter_modules(path, prefix):
+ yield importer, name, ispkg
+
+ if ispkg:
+ try:
+ __import__(name)
+ except ImportError:
+ if onerror is not None:
+ onerror(name)
+ except Exception:
+ if onerror is not None:
+ onerror(name)
+ else:
+ raise
+ else:
+ path = getattr(sys.modules[name], '__path__', None) or []
+
+ # don't traverse path items we've seen before
+ path = [p for p in path if not seen(p)]
+
+ for item in walk_packages(path, name+'.', onerror):
+ yield item
+
+
+def iter_modules(path=None, prefix=''):
+ """Yields (module_loader, name, ispkg) for all submodules on path,
+ or, if path is None, all top-level modules on sys.path.
+
+ 'path' should be either None or a list of paths to look for
+ modules in.
+
+ 'prefix' is a string to output on the front of every module name
+ on output.
+ """
+
+ if path is None:
+ importers = iter_importers()
+ else:
+ importers = map(get_importer, path)
+
+ yielded = {}
+ for i in importers:
+ for name, ispkg in iter_importer_modules(i, prefix):
+ if name not in yielded:
+ yielded[name] = 1
+ yield i, name, ispkg
+
+
+#@simplegeneric
+def iter_importer_modules(importer, prefix=''):
+ if not hasattr(importer, 'iter_modules'):
+ return []
+ return importer.iter_modules(prefix)
+
+iter_importer_modules = simplegeneric(iter_importer_modules)
+
+
+class ImpImporter:
+ """PEP 302 Importer that wraps Python's "classic" import algorithm
+
+ ImpImporter(dirname) produces a PEP 302 importer that searches that
+ directory. ImpImporter(None) produces a PEP 302 importer that searches
+ the current sys.path, plus any modules that are frozen or built-in.
+
+ Note that ImpImporter does not currently support being used by placement
+ on sys.meta_path.
+ """
+
+ def __init__(self, path=None):
+ self.path = path
+
+ def find_module(self, fullname, path=None):
+ # Note: we ignore 'path' argument since it is only used via meta_path
+ subname = fullname.split(".")[-1]
+ if subname != fullname and self.path is None:
+ return None
+ if self.path is None:
+ path = None
+ else:
+ path = [os.path.realpath(self.path)]
+ try:
+ file, filename, etc = imp.find_module(subname, path)
+ except ImportError:
+ return None
+ return ImpLoader(fullname, file, filename, etc)
+
+ def iter_modules(self, prefix=''):
+ if self.path is None or not os.path.isdir(self.path):
+ return
+
+ yielded = {}
+ import inspect
+ try:
+ filenames = os.listdir(self.path)
+ except OSError:
+ # ignore unreadable directories like import does
+ filenames = []
+ filenames.sort() # handle packages before same-named modules
+
+ for fn in filenames:
+ modname = inspect.getmodulename(fn)
+ if modname=='__init__' or modname in yielded:
+ continue
+
+ path = os.path.join(self.path, fn)
+ ispkg = False
+
+ if not modname and os.path.isdir(path) and '.' not in fn:
+ modname = fn
+ try:
+ dircontents = os.listdir(path)
+ except OSError:
+ # ignore unreadable directories like import does
+ dircontents = []
+ for fn in dircontents:
+ subname = inspect.getmodulename(fn)
+ if subname=='__init__':
+ ispkg = True
+ break
+ else:
+ continue # not a package
+
+ if modname and '.' not in modname:
+ yielded[modname] = 1
+ yield prefix + modname, ispkg
+
+
+class ImpLoader:
+ """PEP 302 Loader that wraps Python's "classic" import algorithm
+ """
+ code = source = None
+
+ def __init__(self, fullname, file, filename, etc):
+ self.file = file
+ self.filename = filename
+ self.fullname = fullname
+ self.etc = etc
+
+ def load_module(self, fullname):
+ self._reopen()
+ try:
+ mod = imp.load_module(fullname, self.file, self.filename, self.etc)
+ finally:
+ if self.file:
+ self.file.close()
+ # Note: we don't set __loader__ because we want the module to look
+ # normal; i.e. this is just a wrapper for standard import machinery
+ return mod
+
+ def get_data(self, pathname):
+ return open(pathname, "rb").read()
+
+ def _reopen(self):
+ if self.file and self.file.closed:
+ mod_type = self.etc[2]
+ if mod_type==imp.PY_SOURCE:
+ self.file = open(self.filename, 'rU')
+ elif mod_type in (imp.PY_COMPILED, imp.C_EXTENSION):
+ self.file = open(self.filename, 'rb')
+
+ def _fix_name(self, fullname):
+ if fullname is None:
+ fullname = self.fullname
+ elif fullname != self.fullname:
+ raise ImportError("Loader for module %s cannot handle "
+ "module %s" % (self.fullname, fullname))
+ return fullname
+
+ def is_package(self, fullname):
+ fullname = self._fix_name(fullname)
+ return self.etc[2]==imp.PKG_DIRECTORY
+
+ def get_code(self, fullname=None):
+ fullname = self._fix_name(fullname)
+ if self.code is None:
+ mod_type = self.etc[2]
+ if mod_type==imp.PY_SOURCE:
+ source = self.get_source(fullname)
+ self.code = compile(source, self.filename, 'exec')
+ elif mod_type==imp.PY_COMPILED:
+ self._reopen()
+ try:
+ self.code = read_code(self.file)
+ finally:
+ self.file.close()
+ elif mod_type==imp.PKG_DIRECTORY:
+ self.code = self._get_delegate().get_code()
+ return self.code
+
+ def get_source(self, fullname=None):
+ fullname = self._fix_name(fullname)
+ if self.source is None:
+ mod_type = self.etc[2]
+ if mod_type==imp.PY_SOURCE:
+ self._reopen()
+ try:
+ self.source = self.file.read()
+ finally:
+ self.file.close()
+ elif mod_type==imp.PY_COMPILED:
+ if os.path.exists(self.filename[:-1]):
+ f = open(self.filename[:-1], 'rU')
+ self.source = f.read()
+ f.close()
+ elif mod_type==imp.PKG_DIRECTORY:
+ self.source = self._get_delegate().get_source()
+ return self.source
+
+
+ def _get_delegate(self):
+ return ImpImporter(self.filename).find_module('__init__')
+
+ def get_filename(self, fullname=None):
+ fullname = self._fix_name(fullname)
+ mod_type = self.etc[2]
+ if self.etc[2]==imp.PKG_DIRECTORY:
+ return self._get_delegate().get_filename()
+ elif self.etc[2] in (imp.PY_SOURCE, imp.PY_COMPILED, imp.C_EXTENSION):
+ return self.filename
+ return None
+
+
+try:
+ import zipimport
+ from zipimport import zipimporter
+
+ def iter_zipimport_modules(importer, prefix=''):
+ dirlist = zipimport._zip_directory_cache[importer.archive].keys()
+ dirlist.sort()
+ _prefix = importer.prefix
+ plen = len(_prefix)
+ yielded = {}
+ import inspect
+ for fn in dirlist:
+ if not fn.startswith(_prefix):
+ continue
+
+ fn = fn[plen:].split(os.sep)
+
+ if len(fn)==2 and fn[1].startswith('__init__.py'):
+ if fn[0] not in yielded:
+ yielded[fn[0]] = 1
+ yield fn[0], True
+
+ if len(fn)!=1:
+ continue
+
+ modname = inspect.getmodulename(fn[0])
+ if modname=='__init__':
+ continue
+
+ if modname and '.' not in modname and modname not in yielded:
+ yielded[modname] = 1
+ yield prefix + modname, False
+
+ iter_importer_modules.register(zipimporter, iter_zipimport_modules)
+
+except ImportError:
+ pass
+
+
+def get_importer(path_item):
+ """Retrieve a PEP 302 importer for the given path item
+
+ The returned importer is cached in sys.path_importer_cache
+ if it was newly created by a path hook.
+
+ If there is no importer, a wrapper around the basic import
+ machinery is returned. This wrapper is never inserted into
+ the importer cache (None is inserted instead).
+
+ The cache (or part of it) can be cleared manually if a
+ rescan of sys.path_hooks is necessary.
+ """
+ try:
+ importer = sys.path_importer_cache[path_item]
+ except KeyError:
+ for path_hook in sys.path_hooks:
+ try:
+ importer = path_hook(path_item)
+ break
+ except ImportError:
+ pass
+ else:
+ importer = None
+ sys.path_importer_cache.setdefault(path_item, importer)
+
+ if importer is None:
+ try:
+ importer = ImpImporter(path_item)
+ except ImportError:
+ importer = None
+ return importer
+
+
+def iter_importers(fullname=""):
+ """Yield PEP 302 importers for the given module name
+
+ If fullname contains a '.', the importers will be for the package
+ containing fullname, otherwise they will be importers for sys.meta_path,
+ sys.path, and Python's "classic" import machinery, in that order. If
+ the named module is in a package, that package is imported as a side
+ effect of invoking this function.
+
+ Non PEP 302 mechanisms (e.g. the Windows registry) used by the
+ standard import machinery to find files in alternative locations
+ are partially supported, but are searched AFTER sys.path. Normally,
+ these locations are searched BEFORE sys.path, preventing sys.path
+ entries from shadowing them.
+
+ For this to cause a visible difference in behaviour, there must
+ be a module or package name that is accessible via both sys.path
+ and one of the non PEP 302 file system mechanisms. In this case,
+ the emulation will find the former version, while the builtin
+ import mechanism will find the latter.
+
+ Items of the following types can be affected by this discrepancy:
+ imp.C_EXTENSION, imp.PY_SOURCE, imp.PY_COMPILED, imp.PKG_DIRECTORY
+ """
+ if fullname.startswith('.'):
+ raise ImportError("Relative module names not supported")
+ if '.' in fullname:
+ # Get the containing package's __path__
+ pkg = '.'.join(fullname.split('.')[:-1])
+ if pkg not in sys.modules:
+ __import__(pkg)
+ path = getattr(sys.modules[pkg], '__path__', None) or []
+ else:
+ for importer in sys.meta_path:
+ yield importer
+ path = sys.path
+ for item in path:
+ yield get_importer(item)
+ if '.' not in fullname:
+ yield ImpImporter()
+
+def get_loader(module_or_name):
+ """Get a PEP 302 "loader" object for module_or_name
+
+ If the module or package is accessible via the normal import
+ mechanism, a wrapper around the relevant part of that machinery
+ is returned. Returns None if the module cannot be found or imported.
+ If the named module is not already imported, its containing package
+ (if any) is imported, in order to establish the package __path__.
+
+ This function uses iter_importers(), and is thus subject to the same
+ limitations regarding platform-specific special import locations such
+ as the Windows registry.
+ """
+ if module_or_name in sys.modules:
+ module_or_name = sys.modules[module_or_name]
+ if isinstance(module_or_name, ModuleType):
+ module = module_or_name
+ loader = getattr(module, '__loader__', None)
+ if loader is not None:
+ return loader
+ fullname = module.__name__
+ else:
+ fullname = module_or_name
+ return find_loader(fullname)
+
+def find_loader(fullname):
+ """Find a PEP 302 "loader" object for fullname
+
+ If fullname contains dots, path must be the containing package's __path__.
+ Returns None if the module cannot be found or imported. This function uses
+ iter_importers(), and is thus subject to the same limitations regarding
+ platform-specific special import locations such as the Windows registry.
+ """
+ for importer in iter_importers(fullname):
+ loader = importer.find_module(fullname)
+ if loader is not None:
+ return loader
+
+ return None
+
+
+def extend_path(path, name):
+ """Extend a package's path.
+
+ Intended use is to place the following code in a package's __init__.py:
+
+ from pkgutil import extend_path
+ __path__ = extend_path(__path__, __name__)
+
+ This will add to the package's __path__ all subdirectories of
+ directories on sys.path named after the package. This is useful
+ if one wants to distribute different parts of a single logical
+ package as multiple directories.
+
+ It also looks for *.pkg files beginning where * matches the name
+ argument. This feature is similar to *.pth files (see site.py),
+ except that it doesn't special-case lines starting with 'import'.
+ A *.pkg file is trusted at face value: apart from checking for
+ duplicates, all entries found in a *.pkg file are added to the
+ path, regardless of whether they are exist the filesystem. (This
+ is a feature.)
+
+ If the input path is not a list (as is the case for frozen
+ packages) it is returned unchanged. The input path is not
+ modified; an extended copy is returned. Items are only appended
+ to the copy at the end.
+
+ It is assumed that sys.path is a sequence. Items of sys.path that
+ are not (unicode or 8-bit) strings referring to existing
+ directories are ignored. Unicode items of sys.path that cause
+ errors when used as filenames may cause this function to raise an
+ exception (in line with os.path.isdir() behavior).
+ """
+
+ if not isinstance(path, list):
+ # This could happen e.g. when this is called from inside a
+ # frozen package. Return the path unchanged in that case.
+ return path
+
+ pname = os.path.join(*name.split('.')) # Reconstitute as relative path
+ # Just in case os.extsep != '.'
+ sname = os.extsep.join(name.split('.'))
+ sname_pkg = sname + os.extsep + "pkg"
+ init_py = "__init__" + os.extsep + "py"
+
+ path = path[:] # Start with a copy of the existing path
+
+ for dir in sys.path:
+ if not isinstance(dir, basestring) or not os.path.isdir(dir):
+ continue
+ subdir = os.path.join(dir, pname)
+ # XXX This may still add duplicate entries to path on
+ # case-insensitive filesystems
+ initfile = os.path.join(subdir, init_py)
+ if subdir not in path and os.path.isfile(initfile):
+ path.append(subdir)
+ # XXX Is this the right thing for subpackages like zope.app?
+ # It looks for a file named "zope.app.pkg"
+ pkgfile = os.path.join(dir, sname_pkg)
+ if os.path.isfile(pkgfile):
+ try:
+ f = open(pkgfile)
+ except IOError, msg:
+ sys.stderr.write("Can't open %s: %s\n" %
+ (pkgfile, msg))
+ else:
+ for line in f:
+ line = line.rstrip('\n')
+ if not line or line.startswith('#'):
+ continue
+ path.append(line) # Don't check for existence!
+ f.close()
+
+ return path
+
+def get_data(package, resource):
+ """Get a resource from a package.
+
+ This is a wrapper round the PEP 302 loader get_data API. The package
+ argument should be the name of a package, in standard module format
+ (foo.bar). The resource argument should be in the form of a relative
+ filename, using '/' as the path separator. The parent directory name '..'
+ is not allowed, and nor is a rooted name (starting with a '/').
+
+ The function returns a binary string, which is the contents of the
+ specified resource.
+
+ For packages located in the filesystem, which have already been imported,
+ this is the rough equivalent of
+
+ d = os.path.dirname(sys.modules[package].__file__)
+ data = open(os.path.join(d, resource), 'rb').read()
+
+ If the package cannot be located or loaded, or it uses a PEP 302 loader
+ which does not support get_data(), then None is returned.
+ """
+
+ loader = get_loader(package)
+ if loader is None or not hasattr(loader, 'get_data'):
+ return None
+ mod = sys.modules.get(package) or loader.load_module(package)
+ if mod is None or not hasattr(mod, '__file__'):
+ return None
+
+ # Modify the resource name to be compatible with the loader.get_data
+ # signature - an os.path format "filename" starting with the dirname of
+ # the package's __file__
+ parts = resource.split('/')
+ parts.insert(0, os.path.dirname(mod.__file__))
+ resource_name = os.path.join(*parts)
+ return loader.get_data(resource_name)
diff --git a/python/helpers/pydev/third_party/pluginbase.py b/python/helpers/pydev/third_party/pluginbase.py
new file mode 100644
index 000000000000..0ad6404eee00
--- /dev/null
+++ b/python/helpers/pydev/third_party/pluginbase.py
@@ -0,0 +1,454 @@
+# -*- coding: utf-8 -*-
+"""
+ pluginbase
+ ~~~~~~~~~~
+
+ Pluginbase is a module for Python that provides a system for building
+ plugin based applications.
+
+ :copyright: (c) Copyright 2014 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import os
+import sys
+
+from pydevd_constants import IS_PY24, IS_PY3K
+
+
+if IS_PY24:
+ from third_party.uuid_old import uuid4
+else:
+ from uuid import uuid4
+
+if IS_PY3K:
+ import pkgutil
+else:
+ import pkgutil_old as pkgutil
+
+import errno
+try:
+ from hashlib import md5
+except ImportError:
+ from md5 import md5
+import threading
+
+from types import ModuleType
+from weakref import ref as weakref
+
+
+PY2 = sys.version_info[0] == 2
+if PY2:
+ text_type = unicode
+ string_types = (unicode, str)
+ from cStringIO import StringIO as NativeBytesIO
+else:
+ text_type = str
+ string_types = (str,)
+ from io import BytesIO as NativeBytesIO
+
+
+_local = threading.local()
+
+_internalspace = ModuleType(__name__ + '._internalspace')
+_internalspace.__path__ = []
+sys.modules[_internalspace.__name__] = _internalspace
+
+
+def get_plugin_source(module=None, stacklevel=None):
+ """Returns the :class:`PluginSource` for the current module or the given
+ module. The module can be provided by name (in which case an import
+ will be attempted) or as a module object.
+
+ If no plugin source can be discovered, the return value from this method
+ is `None`.
+
+ This function can be very useful if additional data has been attached
+ to the plugin source. For instance this could allow plugins to get
+ access to a back reference to the application that created them.
+
+ :param module: optionally the module to locate the plugin source of.
+ :param stacklevel: defines how many levels up the module should search
+ for before it discovers the plugin frame. The
+ default is 0. This can be useful for writing wrappers
+ around this function.
+ """
+ if module is None:
+ frm = sys._getframe((stacklevel or 0) + 1)
+ name = frm.f_globals['__name__']
+ glob = frm.f_globals
+ elif isinstance(module, string_types):
+ frm = sys._getframe(1)
+ name = module
+ glob = __import__(module, frm.f_globals,
+ frm.f_locals, ['__dict__']).__dict__
+ else:
+ name = module.__name__
+ glob = module.__dict__
+ return _discover_space(name, glob)
+
+
+def _discover_space(name, globals):
+ try:
+ return _local.space_stack[-1]
+ except (AttributeError, IndexError):
+ pass
+
+ if '__pluginbase_state__' in globals:
+ return globals['__pluginbase_state__'].source
+
+ mod_name = globals.get('__name__')
+ if mod_name is not None and \
+ mod_name.startswith(_internalspace.__name__ + '.'):
+ end = mod_name.find('.', len(_internalspace.__name__) + 1)
+ space = sys.modules.get(mod_name[:end])
+ if space is not None:
+ return space.__pluginbase_state__.source
+
+
+def _shutdown_module(mod):
+ members = list(mod.__dict__.items())
+ for key, value in members:
+ if key[:1] != '_':
+ setattr(mod, key, None)
+ for key, value in members:
+ setattr(mod, key, None)
+
+
+def _to_bytes(s):
+ if isinstance(s, text_type):
+ return s.encode('utf-8')
+ return s
+
+
+class _IntentionallyEmptyModule(ModuleType):
+
+ def __getattr__(self, name):
+ try:
+ return ModuleType.__getattr__(self, name)
+ except AttributeError:
+ if name[:2] == '__':
+ raise
+ raise RuntimeError(
+ 'Attempted to import from a plugin base module (%s) without '
+ 'having a plugin source activated. To solve this error '
+ 'you have to move the import into a "with" block of the '
+ 'associated plugin source.' % self.__name__)
+
+
+class _PluginSourceModule(ModuleType):
+
+ def __init__(self, source):
+ modname = '%s.%s' % (_internalspace.__name__, source.spaceid)
+ ModuleType.__init__(self, modname)
+ self.__pluginbase_state__ = PluginBaseState(source)
+
+ @property
+ def __path__(self):
+ try:
+ ps = self.__pluginbase_state__.source
+ except AttributeError:
+ return []
+ return ps.searchpath + ps.base.searchpath
+
+
+def _setup_base_package(module_name):
+ try:
+ mod = __import__(module_name, None, None, ['__name__'])
+ except ImportError:
+ mod = None
+ if '.' in module_name:
+ parent_mod = __import__(module_name.rsplit('.', 1)[0],
+ None, None, ['__name__'])
+ else:
+ parent_mod = None
+
+ if mod is None:
+ mod = _IntentionallyEmptyModule(module_name)
+ if parent_mod is not None:
+ setattr(parent_mod, module_name.rsplit('.', 1)[-1], mod)
+ sys.modules[module_name] = mod
+
+
+class PluginBase(object):
+ """The plugin base acts as a control object around a dummy Python
+ package that acts as a container for plugins. Usually each
+ application creates exactly one base object for all plugins.
+
+ :param package: the name of the package that acts as the plugin base.
+ Usually this module does not exist. Unless you know
+ what you are doing you should not create this module
+ on the file system.
+ :param searchpath: optionally a shared search path for modules that
+ will be used by all plugin sources registered.
+ """
+
+ def __init__(self, package, searchpath=None):
+ #: the name of the dummy package.
+ self.package = package
+ if searchpath is None:
+ searchpath = []
+ #: the default search path shared by all plugins as list.
+ self.searchpath = searchpath
+ _setup_base_package(package)
+
+ def make_plugin_source(self, *args, **kwargs):
+ """Creats a plugin source for this plugin base and returns it.
+ All parameters are forwarded to :class:`PluginSource`.
+ """
+ return PluginSource(self, *args, **kwargs)
+
+
+class PluginSource(object):
+ """The plugin source is what ultimately decides where plugins are
+ loaded from. Plugin bases can have multiple plugin sources which act
+ as isolation layer. While this is not a security system it generally
+ is not possible for plugins from different sources to accidentally
+ cross talk.
+
+ Once a plugin source has been created it can be used in a ``with``
+ statement to change the behavior of the ``import`` statement in the
+ block to define which source to load the plugins from::
+
+ plugin_source = plugin_base.make_plugin_source(
+ searchpath=['./path/to/plugins', './path/to/more/plugins'])
+
+ with plugin_source:
+ from myapplication.plugins import my_plugin
+
+ :param base: the base this plugin source belongs to.
+ :param identifier: optionally a stable identifier. If it's not defined
+ a random identifier is picked. It's useful to set this
+ to a stable value to have consistent tracebacks
+ between restarts and to support pickle.
+ :param searchpath: a list of paths where plugins are looked for.
+ :param persist: optionally this can be set to `True` and the plugins
+ will not be cleaned up when the plugin source gets
+ garbage collected.
+ """
+ # Set these here to false by default so that a completely failing
+ # constructor does not fuck up the destructor.
+ persist = False
+ mod = None
+
+ def __init__(self, base, identifier=None, searchpath=None,
+ persist=False):
+ #: indicates if this plugin source persists or not.
+ self.persist = persist
+ if identifier is None:
+ identifier = str(uuid4())
+ #: the identifier for this source.
+ self.identifier = identifier
+ #: A reference to the plugin base that created this source.
+ self.base = base
+ #: a list of paths where plugins are searched in.
+ self.searchpath = searchpath
+ #: The internal module name of the plugin source as it appears
+ #: in the :mod:`pluginsource._internalspace`.
+ div = None
+ self.spaceid = '_sp' + md5(
+ _to_bytes(self.base.package) + _to_bytes('|') +
+ _to_bytes(self.identifier)
+ ).hexdigest()
+ #: a reference to the module on the internal
+ #: :mod:`pluginsource._internalspace`.
+ self.mod = _PluginSourceModule(self)
+
+ if hasattr(_internalspace, self.spaceid):
+ raise RuntimeError('This plugin source already exists.')
+ sys.modules[self.mod.__name__] = self.mod
+ setattr(_internalspace, self.spaceid, self.mod)
+
+ def __del__(self):
+ if not self.persist:
+ self.cleanup()
+
+ def list_plugins(self):
+ """Returns a sorted list of all plugins that are available in this
+ plugin source. This can be useful to automatically discover plugins
+ that are available and is usually used together with
+ :meth:`load_plugin`.
+ """
+ rv = []
+ for _, modname, ispkg in pkgutil.iter_modules(self.mod.__path__):
+ rv.append(modname)
+ return sorted(rv)
+
+ def load_plugin(self, name):
+ """This automatically loads a plugin by the given name from the
+ current source and returns the module. This is a convenient
+ alternative to the import statement and saves you from invoking
+ ``__import__`` or a similar function yourself.
+
+ :param name: the name of the plugin to load.
+ """
+ if '.' in name:
+ raise ImportError('Plugin names cannot contain dots.')
+
+ #with self:
+ # return __import__(self.base.package + '.' + name,
+ # globals(), {}, ['__name__'])
+
+ self.__assert_not_cleaned_up()
+ _local.__dict__.setdefault('space_stack', []).append(self)
+ try:
+ res = __import__(self.base.package + '.' + name,
+ globals(), {}, ['__name__'])
+ return res
+ finally:
+ try:
+ _local.space_stack.pop()
+ except (AttributeError, IndexError):
+ pass
+
+ def open_resource(self, plugin, filename):
+ """This function locates a resource inside the plugin and returns
+ a byte stream to the contents of it. If the resource cannot be
+ loaded an :exc:`IOError` will be raised. Only plugins that are
+ real Python packages can contain resources. Plain old Python
+ modules do not allow this for obvious reasons.
+
+ .. versionadded:: 0.3
+
+ :param plugin: the name of the plugin to open the resource of.
+ :param filename: the name of the file within the plugin to open.
+ """
+ mod = self.load_plugin(plugin)
+ fn = getattr(mod, '__file__', None)
+ if fn is not None:
+ if fn.endswith(('.pyc', '.pyo')):
+ fn = fn[:-1]
+ if os.path.isfile(fn):
+ return open(os.path.join(os.path.dirname(fn), filename), 'rb')
+ buf = pkgutil.get_data(self.mod.__name__ + '.' + plugin, filename)
+ if buf is None:
+ raise IOError(errno.ENOEXITS, 'Could not find resource')
+ return NativeBytesIO(buf)
+
+ def cleanup(self):
+ """Cleans up all loaded plugins manually. This is necessary to
+ call only if :attr:`persist` is enabled. Otherwise this happens
+ automatically when the source gets garbage collected.
+ """
+ self.__cleanup()
+
+ def __cleanup(self, _sys=sys, _shutdown_module=_shutdown_module):
+ # The default parameters are necessary because this can be fired
+ # from the destructor and so late when the interpreter shuts down
+ # that these functions and modules might be gone.
+ if self.mod is None:
+ return
+ modname = self.mod.__name__
+ self.mod.__pluginbase_state__ = None
+ self.mod = None
+ try:
+ delattr(_internalspace, self.spaceid)
+ except AttributeError:
+ pass
+ prefix = modname + '.'
+ _sys.modules.pop(modname)
+ for key, value in list(_sys.modules.items()):
+ if not key.startswith(prefix):
+ continue
+ mod = _sys.modules.pop(key, None)
+ if mod is None:
+ continue
+ _shutdown_module(mod)
+
+ def __assert_not_cleaned_up(self):
+ if self.mod is None:
+ raise RuntimeError('The plugin source was already cleaned up.')
+
+ def __enter__(self):
+ self.__assert_not_cleaned_up()
+ _local.__dict__.setdefault('space_stack', []).append(self)
+ return self
+
+ def __exit__(self, exc_type, exc_value, tb):
+ try:
+ _local.space_stack.pop()
+ except (AttributeError, IndexError):
+ pass
+
+ def _rewrite_module_path(self, modname):
+ self.__assert_not_cleaned_up()
+ if modname == self.base.package:
+ return self.mod.__name__
+ elif modname.startswith(self.base.package + '.'):
+ pieces = modname.split('.')
+ return self.mod.__name__ + '.' + '.'.join(
+ pieces[self.base.package.count('.') + 1:])
+
+
+class PluginBaseState(object):
+ __slots__ = ('_source',)
+
+ def __init__(self, source):
+ if source.persist:
+ self._source = lambda: source
+ else:
+ self._source = weakref(source)
+
+ @property
+ def source(self):
+ rv = self._source()
+ if rv is None:
+ raise AttributeError('Plugin source went away')
+ return rv
+
+
+class _ImportHook(ModuleType):
+
+ def __init__(self, name, system_import):
+ ModuleType.__init__(self, name)
+ self._system_import = system_import
+ self.enabled = True
+
+ def enable(self):
+ """Enables the import hook which drives the plugin base system.
+ This is the default.
+ """
+ self.enabled = True
+
+ def disable(self):
+ """Disables the import hook and restores the default import system
+ behavior. This effectively breaks pluginbase but can be useful
+ for testing purposes.
+ """
+ self.enabled = False
+
+ def plugin_import(self, name, globals=None, locals=None,
+ fromlist=None, level=-2):
+ import_name = name
+ if self.enabled:
+ ref_globals = globals
+ if ref_globals is None:
+ ref_globals = sys._getframe(1).f_globals
+ space = _discover_space(name, ref_globals)
+ if space is not None:
+ actual_name = space._rewrite_module_path(name)
+ if actual_name is not None:
+ import_name = actual_name
+ if level == -2:
+ # fake impossible value; default value depends on version
+ if IS_PY24:
+ # the level parameter was added in version 2.5
+ return self._system_import(import_name, globals, locals, fromlist)
+ elif IS_PY3K:
+ # default value for level parameter in python 3
+ level = 0
+ else:
+ # default value for level parameter in other versions
+ level = -1
+ return self._system_import(import_name, globals, locals,
+ fromlist, level)
+
+
+try:
+ import __builtin__ as builtins
+except ImportError:
+ import builtins
+
+import_hook = _ImportHook(__name__ + '.import_hook', builtins.__import__)
+builtins.__import__ = import_hook.plugin_import
+sys.modules[import_hook.__name__] = import_hook
+del builtins
diff --git a/python/helpers/pydev/third_party/uuid_old.py b/python/helpers/pydev/third_party/uuid_old.py
new file mode 100644
index 000000000000..ae3da25ca557
--- /dev/null
+++ b/python/helpers/pydev/third_party/uuid_old.py
@@ -0,0 +1,541 @@
+r"""UUID objects (universally unique identifiers) according to RFC 4122.
+
+This module provides immutable UUID objects (class UUID) and the functions
+uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5
+UUIDs as specified in RFC 4122.
+
+If all you want is a unique ID, you should probably call uuid1() or uuid4().
+Note that uuid1() may compromise privacy since it creates a UUID containing
+the computer's network address. uuid4() creates a random UUID.
+
+Typical usage:
+
+ >>> import uuid
+
+ # make a UUID based on the host ID and current time
+ >>> uuid.uuid1()
+ UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')
+
+ # make a UUID using an MD5 hash of a namespace UUID and a name
+ >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
+ UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')
+
+ # make a random UUID
+ >>> uuid.uuid4()
+ UUID('16fd2706-8baf-433b-82eb-8c7fada847da')
+
+ # make a UUID using a SHA-1 hash of a namespace UUID and a name
+ >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
+ UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')
+
+ # make a UUID from a string of hex digits (braces and hyphens ignored)
+ >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')
+
+ # convert a UUID to a string of hex digits in standard form
+ >>> str(x)
+ '00010203-0405-0607-0809-0a0b0c0d0e0f'
+
+ # get the raw 16 bytes of the UUID
+ >>> x.bytes
+ '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'
+
+ # make a UUID from a 16-byte string
+ >>> uuid.UUID(bytes=x.bytes)
+ UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')
+"""
+
+__author__ = 'Ka-Ping Yee <ping@zesty.ca>'
+
+RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [
+ 'reserved for NCS compatibility', 'specified in RFC 4122',
+ 'reserved for Microsoft compatibility', 'reserved for future definition']
+
+class UUID(object):
+ """Instances of the UUID class represent UUIDs as specified in RFC 4122.
+ UUID objects are immutable, hashable, and usable as dictionary keys.
+ Converting a UUID to a string with str() yields something in the form
+ '12345678-1234-1234-1234-123456789abc'. The UUID constructor accepts
+ five possible forms: a similar string of hexadecimal digits, or a tuple
+ of six integer fields (with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and
+ 48-bit values respectively) as an argument named 'fields', or a string
+ of 16 bytes (with all the integer fields in big-endian order) as an
+ argument named 'bytes', or a string of 16 bytes (with the first three
+ fields in little-endian order) as an argument named 'bytes_le', or a
+ single 128-bit integer as an argument named 'int'.
+
+ UUIDs have these read-only attributes:
+
+ bytes the UUID as a 16-byte string (containing the six
+ integer fields in big-endian byte order)
+
+ bytes_le the UUID as a 16-byte string (with time_low, time_mid,
+ and time_hi_version in little-endian byte order)
+
+ fields a tuple of the six integer fields of the UUID,
+ which are also available as six individual attributes
+ and two derived attributes:
+
+ time_low the first 32 bits of the UUID
+ time_mid the next 16 bits of the UUID
+ time_hi_version the next 16 bits of the UUID
+ clock_seq_hi_variant the next 8 bits of the UUID
+ clock_seq_low the next 8 bits of the UUID
+ node the last 48 bits of the UUID
+
+ time the 60-bit timestamp
+ clock_seq the 14-bit sequence number
+
+ hex the UUID as a 32-character hexadecimal string
+
+ int the UUID as a 128-bit integer
+
+ urn the UUID as a URN as specified in RFC 4122
+
+ variant the UUID variant (one of the constants RESERVED_NCS,
+ RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE)
+
+ version the UUID version number (1 through 5, meaningful only
+ when the variant is RFC_4122)
+ """
+
+ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
+ int=None, version=None):
+ r"""Create a UUID from either a string of 32 hexadecimal digits,
+ a string of 16 bytes as the 'bytes' argument, a string of 16 bytes
+ in little-endian order as the 'bytes_le' argument, a tuple of six
+ integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version,
+ 8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as
+ the 'fields' argument, or a single 128-bit integer as the 'int'
+ argument. When a string of hex digits is given, curly braces,
+ hyphens, and a URN prefix are all optional. For example, these
+ expressions all yield the same UUID:
+
+ UUID('{12345678-1234-5678-1234-567812345678}')
+ UUID('12345678123456781234567812345678')
+ UUID('urn:uuid:12345678-1234-5678-1234-567812345678')
+ UUID(bytes='\x12\x34\x56\x78'*4)
+ UUID(bytes_le='\x78\x56\x34\x12\x34\x12\x78\x56' +
+ '\x12\x34\x56\x78\x12\x34\x56\x78')
+ UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678))
+ UUID(int=0x12345678123456781234567812345678)
+
+ Exactly one of 'hex', 'bytes', 'bytes_le', 'fields', or 'int' must
+ be given. The 'version' argument is optional; if given, the resulting
+ UUID will have its variant and version set according to RFC 4122,
+ overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'.
+ """
+
+ if [hex, bytes, bytes_le, fields, int].count(None) != 4:
+ raise TypeError('need one of hex, bytes, bytes_le, fields, or int')
+ if hex is not None:
+ hex = hex.replace('urn:', '').replace('uuid:', '')
+ hex = hex.strip('{}').replace('-', '')
+ if len(hex) != 32:
+ raise ValueError('badly formed hexadecimal UUID string')
+ int = long(hex, 16)
+ if bytes_le is not None:
+ if len(bytes_le) != 16:
+ raise ValueError('bytes_le is not a 16-char string')
+ bytes = (bytes_le[3] + bytes_le[2] + bytes_le[1] + bytes_le[0] +
+ bytes_le[5] + bytes_le[4] + bytes_le[7] + bytes_le[6] +
+ bytes_le[8:])
+ if bytes is not None:
+ if len(bytes) != 16:
+ raise ValueError('bytes is not a 16-char string')
+ int = long(('%02x'*16) % tuple(map(ord, bytes)), 16)
+ if fields is not None:
+ if len(fields) != 6:
+ raise ValueError('fields is not a 6-tuple')
+ (time_low, time_mid, time_hi_version,
+ clock_seq_hi_variant, clock_seq_low, node) = fields
+ if not 0 <= time_low < 1<<32L:
+ raise ValueError('field 1 out of range (need a 32-bit value)')
+ if not 0 <= time_mid < 1<<16L:
+ raise ValueError('field 2 out of range (need a 16-bit value)')
+ if not 0 <= time_hi_version < 1<<16L:
+ raise ValueError('field 3 out of range (need a 16-bit value)')
+ if not 0 <= clock_seq_hi_variant < 1<<8L:
+ raise ValueError('field 4 out of range (need an 8-bit value)')
+ if not 0 <= clock_seq_low < 1<<8L:
+ raise ValueError('field 5 out of range (need an 8-bit value)')
+ if not 0 <= node < 1<<48L:
+ raise ValueError('field 6 out of range (need a 48-bit value)')
+ clock_seq = (clock_seq_hi_variant << 8L) | clock_seq_low
+ int = ((time_low << 96L) | (time_mid << 80L) |
+ (time_hi_version << 64L) | (clock_seq << 48L) | node)
+ if int is not None:
+ if not 0 <= int < 1<<128L:
+ raise ValueError('int is out of range (need a 128-bit value)')
+ if version is not None:
+ if not 1 <= version <= 5:
+ raise ValueError('illegal version number')
+ # Set the variant to RFC 4122.
+ int &= ~(0xc000 << 48L)
+ int |= 0x8000 << 48L
+ # Set the version number.
+ int &= ~(0xf000 << 64L)
+ int |= version << 76L
+ self.__dict__['int'] = int
+
+ def __cmp__(self, other):
+ if isinstance(other, UUID):
+ return cmp(self.int, other.int)
+ return NotImplemented
+
+ def __hash__(self):
+ return hash(self.int)
+
+ def __int__(self):
+ return self.int
+
+ def __repr__(self):
+ return 'UUID(%r)' % str(self)
+
+ def __setattr__(self, name, value):
+ raise TypeError('UUID objects are immutable')
+
+ def __str__(self):
+ hex = '%032x' % self.int
+ return '%s-%s-%s-%s-%s' % (
+ hex[:8], hex[8:12], hex[12:16], hex[16:20], hex[20:])
+
+ def get_bytes(self):
+ bytes = ''
+ for shift in range(0, 128, 8):
+ bytes = chr((self.int >> shift) & 0xff) + bytes
+ return bytes
+
+ bytes = property(get_bytes)
+
+ def get_bytes_le(self):
+ bytes = self.bytes
+ return (bytes[3] + bytes[2] + bytes[1] + bytes[0] +
+ bytes[5] + bytes[4] + bytes[7] + bytes[6] + bytes[8:])
+
+ bytes_le = property(get_bytes_le)
+
+ def get_fields(self):
+ return (self.time_low, self.time_mid, self.time_hi_version,
+ self.clock_seq_hi_variant, self.clock_seq_low, self.node)
+
+ fields = property(get_fields)
+
+ def get_time_low(self):
+ return self.int >> 96L
+
+ time_low = property(get_time_low)
+
+ def get_time_mid(self):
+ return (self.int >> 80L) & 0xffff
+
+ time_mid = property(get_time_mid)
+
+ def get_time_hi_version(self):
+ return (self.int >> 64L) & 0xffff
+
+ time_hi_version = property(get_time_hi_version)
+
+ def get_clock_seq_hi_variant(self):
+ return (self.int >> 56L) & 0xff
+
+ clock_seq_hi_variant = property(get_clock_seq_hi_variant)
+
+ def get_clock_seq_low(self):
+ return (self.int >> 48L) & 0xff
+
+ clock_seq_low = property(get_clock_seq_low)
+
+ def get_time(self):
+ return (((self.time_hi_version & 0x0fffL) << 48L) |
+ (self.time_mid << 32L) | self.time_low)
+
+ time = property(get_time)
+
+ def get_clock_seq(self):
+ return (((self.clock_seq_hi_variant & 0x3fL) << 8L) |
+ self.clock_seq_low)
+
+ clock_seq = property(get_clock_seq)
+
+ def get_node(self):
+ return self.int & 0xffffffffffff
+
+ node = property(get_node)
+
+ def get_hex(self):
+ return '%032x' % self.int
+
+ hex = property(get_hex)
+
+ def get_urn(self):
+ return 'urn:uuid:' + str(self)
+
+ urn = property(get_urn)
+
+ def get_variant(self):
+ if not self.int & (0x8000 << 48L):
+ return RESERVED_NCS
+ elif not self.int & (0x4000 << 48L):
+ return RFC_4122
+ elif not self.int & (0x2000 << 48L):
+ return RESERVED_MICROSOFT
+ else:
+ return RESERVED_FUTURE
+
+ variant = property(get_variant)
+
+ def get_version(self):
+ # The version bits are only meaningful for RFC 4122 UUIDs.
+ if self.variant == RFC_4122:
+ return int((self.int >> 76L) & 0xf)
+
+ version = property(get_version)
+
+def _find_mac(command, args, hw_identifiers, get_index):
+ import os
+ for dir in ['', '/sbin/', '/usr/sbin']:
+ executable = os.path.join(dir, command)
+ if not os.path.exists(executable):
+ continue
+
+ try:
+ # LC_ALL to get English output, 2>/dev/null to
+ # prevent output on stderr
+ cmd = 'LC_ALL=C %s %s 2>/dev/null' % (executable, args)
+ pipe = os.popen(cmd)
+ except IOError:
+ continue
+
+ for line in pipe:
+ words = line.lower().split()
+ for i in range(len(words)):
+ if words[i] in hw_identifiers:
+ return int(words[get_index(i)].replace(':', ''), 16)
+ return None
+
+def _ifconfig_getnode():
+ """Get the hardware address on Unix by running ifconfig."""
+
+ # This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes.
+ for args in ('', '-a', '-av'):
+ mac = _find_mac('ifconfig', args, ['hwaddr', 'ether'], lambda i: i+1)
+ if mac:
+ return mac
+
+ import socket
+ ip_addr = socket.gethostbyname(socket.gethostname())
+
+ # Try getting the MAC addr from arp based on our IP address (Solaris).
+ mac = _find_mac('arp', '-an', [ip_addr], lambda i: -1)
+ if mac:
+ return mac
+
+ # This might work on HP-UX.
+ mac = _find_mac('lanscan', '-ai', ['lan0'], lambda i: 0)
+ if mac:
+ return mac
+
+ return None
+
+def _ipconfig_getnode():
+ """Get the hardware address on Windows by running ipconfig.exe."""
+ import os, re
+ dirs = ['', r'c:\windows\system32', r'c:\winnt\system32']
+ try:
+ import ctypes
+ buffer = ctypes.create_string_buffer(300)
+ ctypes.windll.kernel32.GetSystemDirectoryA(buffer, 300)
+ dirs.insert(0, buffer.value.decode('mbcs'))
+ except:
+ pass
+ for dir in dirs:
+ try:
+ pipe = os.popen(os.path.join(dir, 'ipconfig') + ' /all')
+ except IOError:
+ continue
+ for line in pipe:
+ value = line.split(':')[-1].strip().lower()
+ if re.match('([0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value):
+ return int(value.replace('-', ''), 16)
+
+def _netbios_getnode():
+ """Get the hardware address on Windows using NetBIOS calls.
+ See http://support.microsoft.com/kb/118623 for details."""
+ import win32wnet, netbios
+ ncb = netbios.NCB()
+ ncb.Command = netbios.NCBENUM
+ ncb.Buffer = adapters = netbios.LANA_ENUM()
+ adapters._pack()
+ if win32wnet.Netbios(ncb) != 0:
+ return
+ adapters._unpack()
+ for i in range(adapters.length):
+ ncb.Reset()
+ ncb.Command = netbios.NCBRESET
+ ncb.Lana_num = ord(adapters.lana[i])
+ if win32wnet.Netbios(ncb) != 0:
+ continue
+ ncb.Reset()
+ ncb.Command = netbios.NCBASTAT
+ ncb.Lana_num = ord(adapters.lana[i])
+ ncb.Callname = '*'.ljust(16)
+ ncb.Buffer = status = netbios.ADAPTER_STATUS()
+ if win32wnet.Netbios(ncb) != 0:
+ continue
+ status._unpack()
+ bytes = map(ord, status.adapter_address)
+ return ((bytes[0]<<40L) + (bytes[1]<<32L) + (bytes[2]<<24L) +
+ (bytes[3]<<16L) + (bytes[4]<<8L) + bytes[5])
+
+# Thanks to Thomas Heller for ctypes and for his help with its use here.
+
+# If ctypes is available, use it to find system routines for UUID generation.
+_uuid_generate_random = _uuid_generate_time = _UuidCreate = None
+try:
+ import ctypes, ctypes.util
+ _buffer = ctypes.create_string_buffer(16)
+
+ # The uuid_generate_* routines are provided by libuuid on at least
+ # Linux and FreeBSD, and provided by libc on Mac OS X.
+ for libname in ['uuid', 'c']:
+ try:
+ lib = ctypes.CDLL(ctypes.util.find_library(libname))
+ except:
+ continue
+ if hasattr(lib, 'uuid_generate_random'):
+ _uuid_generate_random = lib.uuid_generate_random
+ if hasattr(lib, 'uuid_generate_time'):
+ _uuid_generate_time = lib.uuid_generate_time
+
+ # On Windows prior to 2000, UuidCreate gives a UUID containing the
+ # hardware address. On Windows 2000 and later, UuidCreate makes a
+ # random UUID and UuidCreateSequential gives a UUID containing the
+ # hardware address. These routines are provided by the RPC runtime.
+ # NOTE: at least on Tim's WinXP Pro SP2 desktop box, while the last
+ # 6 bytes returned by UuidCreateSequential are fixed, they don't appear
+ # to bear any relationship to the MAC address of any network device
+ # on the box.
+ try:
+ lib = ctypes.windll.rpcrt4
+ except:
+ lib = None
+ _UuidCreate = getattr(lib, 'UuidCreateSequential',
+ getattr(lib, 'UuidCreate', None))
+except:
+ pass
+
+def _unixdll_getnode():
+ """Get the hardware address on Unix using ctypes."""
+ _uuid_generate_time(_buffer)
+ return UUID(bytes=_buffer.raw).node
+
+def _windll_getnode():
+ """Get the hardware address on Windows using ctypes."""
+ if _UuidCreate(_buffer) == 0:
+ return UUID(bytes=_buffer.raw).node
+
+def _random_getnode():
+ """Get a random node ID, with eighth bit set as suggested by RFC 4122."""
+ import random
+ return random.randrange(0, 1<<48L) | 0x010000000000L
+
+_node = None
+
+def getnode():
+ """Get the hardware address as a 48-bit positive integer.
+
+ The first time this runs, it may launch a separate program, which could
+ be quite slow. If all attempts to obtain the hardware address fail, we
+ choose a random 48-bit number with its eighth bit set to 1 as recommended
+ in RFC 4122.
+ """
+
+ global _node
+ if _node is not None:
+ return _node
+
+ import sys
+ if sys.platform == 'win32':
+ getters = [_windll_getnode, _netbios_getnode, _ipconfig_getnode]
+ else:
+ getters = [_unixdll_getnode, _ifconfig_getnode]
+
+ for getter in getters + [_random_getnode]:
+ try:
+ _node = getter()
+ except:
+ continue
+ if _node is not None:
+ return _node
+
+_last_timestamp = None
+
+def uuid1(node=None, clock_seq=None):
+ """Generate a UUID from a host ID, sequence number, and the current time.
+ If 'node' is not given, getnode() is used to obtain the hardware
+ address. If 'clock_seq' is given, it is used as the sequence number;
+ otherwise a random 14-bit sequence number is chosen."""
+
+ # When the system provides a version-1 UUID generator, use it (but don't
+ # use UuidCreate here because its UUIDs don't conform to RFC 4122).
+ if _uuid_generate_time and node is clock_seq is None:
+ _uuid_generate_time(_buffer)
+ return UUID(bytes=_buffer.raw)
+
+ global _last_timestamp
+ import time
+ nanoseconds = int(time.time() * 1e9)
+ # 0x01b21dd213814000 is the number of 100-ns intervals between the
+ # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
+ timestamp = int(nanoseconds/100) + 0x01b21dd213814000L
+ if timestamp <= _last_timestamp:
+ timestamp = _last_timestamp + 1
+ _last_timestamp = timestamp
+ if clock_seq is None:
+ import random
+ clock_seq = random.randrange(1<<14L) # instead of stable storage
+ time_low = timestamp & 0xffffffffL
+ time_mid = (timestamp >> 32L) & 0xffffL
+ time_hi_version = (timestamp >> 48L) & 0x0fffL
+ clock_seq_low = clock_seq & 0xffL
+ clock_seq_hi_variant = (clock_seq >> 8L) & 0x3fL
+ if node is None:
+ node = getnode()
+ return UUID(fields=(time_low, time_mid, time_hi_version,
+ clock_seq_hi_variant, clock_seq_low, node), version=1)
+
+def uuid3(namespace, name):
+ """Generate a UUID from the MD5 hash of a namespace UUID and a name."""
+ import md5
+ hash = md5.md5(namespace.bytes + name).digest()
+ return UUID(bytes=hash[:16], version=3)
+
+def uuid4():
+ """Generate a random UUID."""
+
+ # When the system provides a version-4 UUID generator, use it.
+ if _uuid_generate_random:
+ _uuid_generate_random(_buffer)
+ return UUID(bytes=_buffer.raw)
+
+ # Otherwise, get randomness from urandom or the 'random' module.
+ try:
+ import os
+ return UUID(bytes=os.urandom(16), version=4)
+ except:
+ import random
+ bytes = [chr(random.randrange(256)) for i in range(16)]
+ return UUID(bytes=bytes, version=4)
+
+def uuid5(namespace, name):
+ """Generate a UUID from the SHA-1 hash of a namespace UUID and a name."""
+ import sha
+ hash = sha.sha(namespace.bytes + name).digest()
+ return UUID(bytes=hash[:16], version=5)
+
+# The following standard UUIDs are for use with uuid3() or uuid5().
+
+NAMESPACE_DNS = UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
+NAMESPACE_URL = UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8')
+NAMESPACE_OID = UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8')
+NAMESPACE_X500 = UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8')
diff --git a/python/ide/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java b/python/ide/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java
index fc165b8893f6..38195d1380b9 100644
--- a/python/ide/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java
+++ b/python/ide/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java
@@ -298,14 +298,14 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
}
final Sdk prevSdk = ProjectRootManager.getInstance(myProject).getProjectSdk();
- final Sdk selectedSdk = setSdk(newSdk);
+ setSdk(newSdk);
// update string literals if different LanguageLevel was selected
- if (prevSdk != null && selectedSdk != null) {
- final PythonSdkFlavor flavor1 = PythonSdkFlavor.getFlavor(selectedSdk);
+ if (prevSdk != null && newSdk != null) {
+ final PythonSdkFlavor flavor1 = PythonSdkFlavor.getFlavor(newSdk);
final PythonSdkFlavor flavor2 = PythonSdkFlavor.getFlavor(prevSdk);
if (flavor1 != null && flavor2 != null) {
- final LanguageLevel languageLevel1 = flavor1.getLanguageLevel(selectedSdk);
+ final LanguageLevel languageLevel1 = flavor1.getLanguageLevel(newSdk);
final LanguageLevel languageLevel2 = flavor2.getLanguageLevel(prevSdk);
if ((languageLevel1.isPy3K() && languageLevel2.isPy3K()) ||
(!languageLevel1.isPy3K()) && !languageLevel2.isPy3K()) {
@@ -316,22 +316,20 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
rehighlightStrings(myProject);
}
- private Sdk setSdk(Sdk item) {
+ private void setSdk(final Sdk item) {
myAddedSdks.clear();
- final Sdk selectedSdk = myProjectSdksModel.findSdk(item);
if (myModule == null) {
final ProjectRootManager rootManager = ProjectRootManager.getInstance(myProject);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
- rootManager.setProjectSdk(selectedSdk);
+ rootManager.setProjectSdk(item);
}
});
}
else {
- ModuleRootModificationUtil.setModuleSdk(myModule, selectedSdk);
+ ModuleRootModificationUtil.setModuleSdk(myModule, item);
}
- return selectedSdk;
}
public static void rehighlightStrings(final @NotNull Project project) {
@@ -429,8 +427,6 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
public void sdkAdded(Sdk sdk) {
final Object item = myConfigurable.mySdkCombo.getSelectedItem();
- myConfigurable.resetSdkList();
-
if (item instanceof PyDetectedSdk) {
final String path = ((PyDetectedSdk)item).getHomePath();
if (path != null && path.equals(sdk.getHomePath()))
diff --git a/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java b/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java
index 1ad9cf7ececc..6514741fda45 100644
--- a/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java
+++ b/python/ide/src/com/jetbrains/python/newProject/actions/AbstractProjectSettingsStep.java
@@ -33,15 +33,11 @@ import com.jetbrains.python.configuration.PyConfigurableInterpreterList;
import com.jetbrains.python.configuration.VirtualEnvProjectFilter;
import com.jetbrains.python.newProject.PyFrameworkProjectGenerator;
import com.jetbrains.python.newProject.PythonProjectGenerator;
-import com.jetbrains.python.packaging.PyExternalProcessException;
-import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageManager;
-import com.jetbrains.python.packaging.PyPackageManagerImpl;
+import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
-import com.jetbrains.python.sdk.flavors.JythonSdkFlavor;
-import com.jetbrains.python.sdk.flavors.PyPySdkFlavor;
-import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
import icons.PythonIcons;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
@@ -79,22 +75,6 @@ abstract public class AbstractProjectSettingsStep extends AbstractActionWithPane
myCallback = callback;
myIsWelcomeScreen = isWelcomeScreen;
myProjectDirectory = FileUtil.findSequentNonexistentFile(new File(ProjectUtil.getBaseDir()), "untitled", "");
- if (myProjectGenerator instanceof WebProjectTemplate) {
- ((WebProjectTemplate)myProjectGenerator).getPeer().addSettingsStateListener(new WebProjectGenerator.SettingsStateListener() {
- @Override
- public void stateChanged(boolean validSettings) {
- checkValid();
- }
- });
- }
- else if (myProjectGenerator instanceof PythonProjectGenerator) {
- ((PythonProjectGenerator)myProjectGenerator).addSettingsStateListener(new PythonProjectGenerator.SettingsListener() {
- @Override
- public void stateChanged() {
- checkValid();
- }
- });
- }
myCreateAction = new AnAction("Create", "Create Project", getIcon()) {
@Override
@@ -112,6 +92,7 @@ abstract public class AbstractProjectSettingsStep extends AbstractActionWithPane
@Override
public JPanel createPanel() {
+ initGeneratorListeners();
final JPanel basePanel = createBasePanel();
final JPanel mainPanel = new JPanel(new BorderLayout());
@@ -142,6 +123,25 @@ abstract public class AbstractProjectSettingsStep extends AbstractActionWithPane
return mainPanel;
}
+ private void initGeneratorListeners() {
+ if (myProjectGenerator instanceof WebProjectTemplate) {
+ ((WebProjectTemplate)myProjectGenerator).getPeer().addSettingsStateListener(new WebProjectGenerator.SettingsStateListener() {
+ @Override
+ public void stateChanged(boolean validSettings) {
+ checkValid();
+ }
+ });
+ }
+ else if (myProjectGenerator instanceof PythonProjectGenerator) {
+ ((PythonProjectGenerator)myProjectGenerator).addSettingsStateListener(new PythonProjectGenerator.SettingsListener() {
+ @Override
+ public void stateChanged() {
+ checkValid();
+ }
+ });
+ }
+ }
+
protected Icon getIcon() {
return myProjectGenerator.getLogo();
}
@@ -319,29 +319,13 @@ abstract public class AbstractProjectSettingsStep extends AbstractActionWithPane
PyFrameworkProjectGenerator frameworkProjectGenerator = (PyFrameworkProjectGenerator)myProjectGenerator;
String frameworkName = frameworkProjectGenerator.getFrameworkTitle();
if (sdk != null && !isFrameworkInstalled(sdk)) {
- final PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk);
- final boolean onlyWithCache =
- PythonSdkFlavor.getFlavor(sdk) instanceof JythonSdkFlavor || PythonSdkFlavor.getFlavor(sdk) instanceof PyPySdkFlavor;
String warningText = frameworkName + " will be installed on selected interpreter";
- try {
- if (onlyWithCache && packageManager.cacheIsNotNull() || !onlyWithCache) {
- final PyPackage pip = packageManager.findInstalledPackage("pip");
- myInstallFramework = true;
- if (pip == null) {
- warningText = "pip and " + warningText;
- }
- setWarningText(warningText);
- }
- }
- catch (PyExternalProcessException ignored) {
- myInstallFramework = true;
- warningText = "pip and " + warningText;
- setWarningText(warningText);
- }
- if (!myInstallFramework) {
- setErrorText("No " + frameworkName + " support installed in selected interpreter");
- return false;
+ myInstallFramework = true;
+ final PyPackageManager packageManager = PyPackageManager.getInstance(sdk);
+ if (!packageManager.hasManagement(PySdkUtil.isRemote(sdk))) {
+ warningText = "Python packaging tools and " + warningText;
}
+ setWarningText(warningText);
}
if (isPy3k && !((PyFrameworkProjectGenerator)myProjectGenerator).supportsPython3()) {
setErrorText(frameworkName + " is not supported for the selected interpreter");
@@ -454,6 +438,10 @@ abstract public class AbstractProjectSettingsStep extends AbstractActionWithPane
return myLocationField.getText();
}
+ public void setLocation(@NotNull final String location) {
+ myLocationField.setText(location);
+ }
+
public boolean installFramework() {
return myInstallFramework;
}
diff --git a/python/ide/src/com/jetbrains/python/newProject/actions/GenerateProjectCallback.java b/python/ide/src/com/jetbrains/python/newProject/actions/GenerateProjectCallback.java
index 2aa9a391512f..2e88b7f2767c 100644
--- a/python/ide/src/com/jetbrains/python/newProject/actions/GenerateProjectCallback.java
+++ b/python/ide/src/com/jetbrains/python/newProject/actions/GenerateProjectCallback.java
@@ -104,7 +104,7 @@ public class GenerateProjectCallback implements NullableConsumer<AbstractProject
}
@Nullable
- private Project generateProject(@NotNull final Project project, @NotNull final AbstractProjectSettingsStep settings) {
+ private static Project generateProject(@NotNull final Project project, @NotNull final AbstractProjectSettingsStep settings) {
final DirectoryProjectGenerator generator = settings.getProjectGenerator();
final File location = new File(settings.getProjectLocation());
if (!location.exists() && !location.mkdirs()) {
diff --git a/python/openapi/src/com/jetbrains/python/documentation/PythonDocumentationQuickInfoProvider.java b/python/openapi/src/com/jetbrains/python/documentation/PythonDocumentationQuickInfoProvider.java
new file mode 100644
index 000000000000..52dc4732133c
--- /dev/null
+++ b/python/openapi/src/com/jetbrains/python/documentation/PythonDocumentationQuickInfoProvider.java
@@ -0,0 +1,25 @@
+package com.jetbrains.python.documentation;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Allows you to inject quick info into python documentation provider
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface PythonDocumentationQuickInfoProvider {
+ ExtensionPointName<PythonDocumentationQuickInfoProvider> EP_NAME =
+ ExtensionPointName.create("Pythonid.pythonDocumentationQuickInfoProvider");
+
+ /**
+ * Return quick info for <strong>original</strong> element.
+ *
+ * @param originalElement original element
+ * @return info (if exists) or null (if another provider should be checked)
+ */
+ @Nullable
+ String getQuickInfo(@NotNull PsiElement originalElement);
+}
diff --git a/python/openapi/src/com/jetbrains/python/packaging/PyPackageManager.java b/python/openapi/src/com/jetbrains/python/packaging/PyPackageManager.java
index 3b2080b00ba1..2a8003c46eaf 100644
--- a/python/openapi/src/com/jetbrains/python/packaging/PyPackageManager.java
+++ b/python/openapi/src/com/jetbrains/python/packaging/PyPackageManager.java
@@ -15,35 +15,45 @@
*/
package com.jetbrains.python.packaging;
-import com.intellij.openapi.project.Project;
+import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.Key;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.awt.*;
+import java.util.List;
+import java.util.Set;
/**
* @author yole
*/
public abstract class PyPackageManager {
+ public static final Key<Boolean> RUNNING_PACKAGING_TASKS = Key.create("PyPackageRequirementsInspection.RunningPackagingTasks");
+
+ public static final String PACKAGE_SETUPTOOLS = "setuptools";
+ public static final String PACKAGE_PIP = "pip";
+ public static final String PACKAGE_DISTRIBUTE = "distribute";
+
+ public static final String USE_USER_SITE = "--user";
+
public static PyPackageManager getInstance(Sdk sdk) {
return PyPackageManagers.getInstance().forSdk(sdk);
}
- /**
- * Returns true if pip is installed for the specific interpreter; returns false if pip is not
- * installed or if it is not currently known whether it's installed (e.g. for a remote interpreter).
- *
- * @return true if pip is known to be installed, false otherwise.
- */
- public abstract boolean hasPip();
- public abstract void install(String requirementString) throws PyExternalProcessException;
- public abstract void showInstallationError(Project project, String title, String description);
- public abstract void showInstallationError(Component owner, String title, String description);
+ public abstract void installManagement() throws PyExternalProcessException;
+ public abstract boolean hasManagement(boolean cachedOnly);
+ public abstract void install(@NotNull String requirementString) throws PyExternalProcessException;
+ public abstract void install(@NotNull List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws PyExternalProcessException;
+ public abstract void uninstall(@NotNull List<PyPackage> packages) throws PyExternalProcessException;
public abstract void refresh();
+ @NotNull
+ public abstract String createVirtualEnv(@NotNull String destinationDir, boolean useGlobalSite) throws PyExternalProcessException;
@Nullable
- public abstract PyPackage findInstalledPackage(String name) throws PyExternalProcessException;
-
- public abstract boolean findPackage(@NotNull final String name);
-
+ public abstract List<PyPackage> getPackages(boolean cachedOnly) throws PyExternalProcessException;
+ @Nullable
+ public abstract PyPackage findPackage(@NotNull String name, boolean cachedOnly) throws PyExternalProcessException;
+ @Nullable
+ public abstract List<PyRequirement> getRequirements(@NotNull Module module);
+ @Nullable
+ public abstract Set<PyPackage> getDependents(@NotNull PyPackage pkg) throws PyExternalProcessException;
}
diff --git a/python/openapi/src/com/jetbrains/python/packaging/PyPackageManagers.java b/python/openapi/src/com/jetbrains/python/packaging/PyPackageManagers.java
index 9f598748aa7b..2b6554040fe7 100644
--- a/python/openapi/src/com/jetbrains/python/packaging/PyPackageManagers.java
+++ b/python/openapi/src/com/jetbrains/python/packaging/PyPackageManagers.java
@@ -16,12 +16,8 @@
package com.jetbrains.python.packaging;
import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
/**
* @author yole
@@ -35,22 +31,4 @@ public abstract class PyPackageManagers {
@NotNull
public abstract PyPackageManager forSdk(Sdk sdk);
-
- /**
- * Returns the list of requirements from 'requirements.txt' or 'setup.py' files in the specified module.
- *
- * @param module the module to check for requirements
- * @return the list of requirements, or null if the module contains neither requirements.txt nor setup.py.
- */
- @Nullable
- public abstract List<PyRequirement> getRequirements(Module module);
-
- /**
- * Returns the list of requirements from 'requirements.txt' file in the specified module.
- *
- * @param module the module to check for requirements
- * @return the list of requirements, or null if the module does not contain a requirements.txt
- */
- @Nullable
- public abstract List<PyRequirement> getRequirementsFromTxt(Module module);
}
diff --git a/python/openapi/src/com/jetbrains/python/templateLanguages/PyTemplatesUtil.java b/python/openapi/src/com/jetbrains/python/templateLanguages/PyTemplatesUtil.java
index e660029fe384..c37c65ed4144 100644
--- a/python/openapi/src/com/jetbrains/python/templateLanguages/PyTemplatesUtil.java
+++ b/python/openapi/src/com/jetbrains/python/templateLanguages/PyTemplatesUtil.java
@@ -40,7 +40,7 @@ public class PyTemplatesUtil {
if (templateBinding != null) {
if (TemplatesService.ALL_TEMPLATE_BINDINGS.contains(templateBinding)) {
try {
- final PyPackage installedPackage = packageManager.findInstalledPackage(templateBinding);
+ final PyPackage installedPackage = packageManager.findPackage(templateBinding, false);
if (installedPackage == null)
return new ValidationResult(templateBinding + " will be installed on selected interpreter");
}
@@ -50,7 +50,7 @@ public class PyTemplatesUtil {
}
if (language != null) {
try {
- final PyPackage installedPackage = packageManager.findInstalledPackage(language);
+ final PyPackage installedPackage = packageManager.findPackage(language, false);
if (installedPackage == null) {
return new ValidationResult(language + " will be installed on selected interpreter");
}
diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java
index bd927c5d49bf..ec04e1b4cb61 100644
--- a/python/psi-api/src/com/jetbrains/python/PyNames.java
+++ b/python/psi-api/src/com/jetbrains/python/PyNames.java
@@ -164,6 +164,8 @@ public class PyNames {
public static final String UNKNOWN_TYPE = "unknown";
+ public static final String UNNAMED_ELEMENT = "<unnamed>";
+
/**
* Contains all known predefined names of "__foo__" form.
*/
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java b/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java
index ab6ccfab0bc6..762225f18f12 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java
@@ -1,6 +1,8 @@
package com.jetbrains.python.debugger;
import com.intellij.execution.ui.ConsoleViewContentType;
+import com.intellij.xdebugger.frame.XValueChildrenList;
+import com.jetbrains.python.debugger.pydev.PyDebugCallback;
import java.io.IOException;
@@ -12,7 +14,7 @@ public interface IPyDebugProcess extends PyFrameAccessor {
void threadSuspended(PyThreadInfo thread);
- boolean isVariable(String name);
+ boolean canSaveToTemp(String name);
void threadResumed(PyThreadInfo thread);
@@ -25,4 +27,6 @@ public interface IPyDebugProcess extends PyFrameAccessor {
void recordSignature(PySignature signature);
void showConsole(PyThreadInfo thread);
+
+ void loadReferrers(PyReferringObjectsValue var, PyDebugCallback<XValueChildrenList> callback);
}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java
index 69b64827c40f..ddec6797ea8d 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/PyDebugValue.java
@@ -5,6 +5,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.xdebugger.frame.XNamedValue;
import com.intellij.xdebugger.frame.*;
+import com.jetbrains.python.debugger.pydev.PyVariableLocator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -21,14 +22,13 @@ public class PyDebugValue extends XNamedValue {
private final String myValue;
private final boolean myContainer;
private final PyDebugValue myParent;
+ private String myId = null;
private final PyFrameAccessor myFrameAccessor;
- private final boolean myErrorOnEval;
+ private PyVariableLocator myVariableLocator;
- public PyDebugValue(@NotNull final String name, final String type, final String value, final boolean container, boolean errorOnEval) {
- this(name, type, value, container, errorOnEval, null, null);
- }
+ private final boolean myErrorOnEval;
public PyDebugValue(@NotNull final String name, final String type, final String value, final boolean container,
boolean errorOnEval, final PyFrameAccessor frameAccessor) {
@@ -97,7 +97,7 @@ public class PyDebugValue extends XNamedValue {
myParent.buildExpression(result);
if (("dict".equals(myParent.getType()) || "list".equals(myParent.getType()) || "tuple".equals(myParent.getType())) &&
!isLen(myName)) {
- result.append('[').append(removeId(myName)).append(']');
+ result.append('[').append(removeLeadingZeros(removeId(myName))).append(']');
}
else if (("set".equals(myParent.getType())) && !isLen(myName)) {
//set doesn't support indexing
@@ -105,6 +105,9 @@ public class PyDebugValue extends XNamedValue {
else if (isLen(myName)) {
result.append('.').append(myName).append("()");
}
+ else if (("ndarray".equals(myParent.getType()) || "matrix".equals(myParent.getType())) && myName.startsWith("[")) {
+ result.append(removeLeadingZeros(myName));
+ }
else {
result.append('.').append(myName);
}
@@ -119,6 +122,11 @@ public class PyDebugValue extends XNamedValue {
return name;
}
+ private static String removeLeadingZeros(@NotNull String name) {
+ //bugs.python.org/issue15254: "0" prefix for octal
+ return name.replaceFirst("^0+(?!$)", "");
+ }
+
private static boolean isLen(String name) {
return "__len__".equals(name);
}
@@ -179,4 +187,39 @@ public class PyDebugValue extends XNamedValue {
public PyDebugValue setName(String newName) {
return new PyDebugValue(newName, myType, myValue, myContainer, myErrorOnEval, myParent, myFrameAccessor);
}
+
+ @Nullable
+ @Override
+ public XReferrersProvider getReferrersProvider() {
+ if (myFrameAccessor.getReferrersLoader() != null) {
+ return new XReferrersProvider() {
+ @Override
+ public XValue getReferringObjectsValue() {
+ return new PyReferringObjectsValue(PyDebugValue.this);
+ }
+ };
+ } else {
+ return null;
+ }
+ }
+
+ public PyFrameAccessor getFrameAccessor() {
+ return myFrameAccessor;
+ }
+
+ public PyVariableLocator getVariableLocator() {
+ return myVariableLocator;
+ }
+
+ public void setVariableLocator(PyVariableLocator variableLocator) {
+ myVariableLocator = variableLocator;
+ }
+
+ public String getId() {
+ return myId;
+ }
+
+ public void setId(String id) {
+ myId = id;
+ }
}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java
index 530035e78a4b..01666456f420 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/PyFrameAccessor.java
@@ -17,4 +17,7 @@ public interface PyFrameAccessor {
XValueChildrenList loadVariable(PyDebugValue var) throws PyDebuggerException;
void changeVariable(PyDebugValue variable, String expression) throws PyDebuggerException;
+
+ @Nullable
+ PyReferrersLoader getReferrersLoader();
}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyReferrersLoader.java b/python/pydevSrc/com/jetbrains/python/debugger/PyReferrersLoader.java
new file mode 100644
index 000000000000..edb2f641b192
--- /dev/null
+++ b/python/pydevSrc/com/jetbrains/python/debugger/PyReferrersLoader.java
@@ -0,0 +1,20 @@
+package com.jetbrains.python.debugger;
+
+import com.intellij.xdebugger.frame.XReferrersProvider;
+import com.intellij.xdebugger.frame.XValueChildrenList;
+import com.jetbrains.python.debugger.pydev.PyDebugCallback;
+
+/**
+ * @author traff
+ */
+public class PyReferrersLoader {
+ private final IPyDebugProcess myProcess;
+
+ public PyReferrersLoader(IPyDebugProcess process) {
+ myProcess = process;
+ }
+
+ public void loadReferrers(PyReferringObjectsValue value, PyDebugCallback<XValueChildrenList> callback) {
+ myProcess.loadReferrers(value, callback);
+ }
+}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyReferringObjectsValue.java b/python/pydevSrc/com/jetbrains/python/debugger/PyReferringObjectsValue.java
new file mode 100644
index 000000000000..283653659d0d
--- /dev/null
+++ b/python/pydevSrc/com/jetbrains/python/debugger/PyReferringObjectsValue.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.debugger;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.xdebugger.frame.XCompositeNode;
+import com.intellij.xdebugger.frame.XValueChildrenList;
+import com.jetbrains.python.debugger.pydev.PyDebugCallback;
+import org.jetbrains.annotations.NotNull;
+
+public class PyReferringObjectsValue extends PyDebugValue {
+ private static final Logger LOG = Logger.getInstance(PyReferringObjectsValue.class);
+
+ private final @NotNull PyReferrersLoader myReferrersLoader;
+
+ public PyReferringObjectsValue(@NotNull String name,
+ String type,
+ String value,
+ boolean container, boolean errorOnEval, @NotNull PyFrameAccessor frameAccessor) {
+ super(name, type, value, container, errorOnEval, frameAccessor);
+ myReferrersLoader = frameAccessor.getReferrersLoader();
+ }
+
+ public PyReferringObjectsValue(PyDebugValue debugValue) {
+ this(debugValue.getName(), debugValue.getType(), debugValue.getValue(), debugValue.isContainer(), debugValue.isErrorOnEval(), debugValue.getFrameAccessor());
+ }
+
+ @Override
+ public boolean canNavigateToSource() {
+ return true;
+ }
+
+ @Override
+ public void computeChildren(@NotNull final XCompositeNode node) {
+ if (node.isObsolete()) return;
+
+ myReferrersLoader.loadReferrers(this, new PyDebugCallback<XValueChildrenList>() {
+ @Override
+ public void ok(XValueChildrenList value) {
+ if (!node.isObsolete()) {
+ node.addChildren(value, true);
+ }
+ }
+
+ @Override
+ public void error(PyDebuggerException e) {
+ if (!node.isObsolete()) {
+ node.setErrorMessage("Unable to display children:" + e.getMessage());
+ }
+ LOG.warn(e);
+ }
+ });
+ }
+
+ public boolean isField() {
+ return false; //TODO
+ }
+}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyThreadInfo.java b/python/pydevSrc/com/jetbrains/python/debugger/PyThreadInfo.java
index c01be15384ab..7fffc636a9fb 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/PyThreadInfo.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/PyThreadInfo.java
@@ -80,7 +80,7 @@ public class PyThreadInfo {
}
public boolean isExceptionBreak() {
- return myStopReason == AbstractCommand.ADD_EXCEPTION_BREAKPOINT || myStopReason == AbstractCommand.ADD_DJANGO_EXCEPTION_BREAKPOINT;
+ return myStopReason == AbstractCommand.ADD_EXCEPTION_BREAKPOINT;
}
@Override
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/PyTypeHandler.java b/python/pydevSrc/com/jetbrains/python/debugger/PyTypeHandler.java
index 6db612488d4b..f882aae4d0e7 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/PyTypeHandler.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/PyTypeHandler.java
@@ -37,6 +37,9 @@ public class PyTypeHandler {
FORMATTERS = new HashMap<String, Formatter>();
FORMATTERS.put("str", STR_FORMATTER);
FORMATTERS.put("unicode", UNI_FORMATTER);
+ //numpy types
+ FORMATTERS.put("string_", STR_FORMATTER);
+ FORMATTERS.put("unicode_", UNI_FORMATTER);
}
private PyTypeHandler() { }
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
index 15bb76b6c3d1..d13addaade04 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java
@@ -28,11 +28,10 @@ public abstract class AbstractCommand<T> {
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 ADD_DJANGO_EXCEPTION_BREAKPOINT = 125;
- public static final int REMOVE_DJANGO_EXCEPTION_BREAKPOINT = 126;
public static final int SMART_STEP_INTO = 128;
public static final int EXIT = 129;
public static final int CALL_SIGNATURE_TRACE = 130;
+ public static final int CMD_RUN_CUSTOM_OPERATION = 135;
public static final int SHOW_CONSOLE = 142;
public static final int ERROR = 901;
@@ -41,7 +40,7 @@ public abstract class AbstractCommand<T> {
public static final String TAB_CHAR = "@_@TAB_CHAR@_@";
- @NotNull protected final RemoteDebugger myDebugger;
+ @NotNull private final RemoteDebugger myDebugger;
private final int myCommandCode;
private final ResponseProcessor<T> myResponseProcessor;
@@ -107,7 +106,7 @@ public abstract class AbstractCommand<T> {
}
}
- public void execute(final ProcessDebugger.DebugCallback<T> callback) {
+ public void execute(final PyDebugCallback<T> callback) {
final int sequence = myDebugger.getNextSequence();
final ResponseProcessor<T> processor = getResponseProcessor();
@@ -186,6 +185,11 @@ public abstract class AbstractCommand<T> {
return command == ERROR;
}
+ @NotNull
+ public RemoteDebugger getDebugger() {
+ return myDebugger;
+ }
+
protected static class Payload {
private final StringBuilder myBuilder = new StringBuilder();
private static final char SEPARATOR = '\t';
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ConsoleExecCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ConsoleExecCommand.java
index 14d8c084a39d..c03890f088e8 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ConsoleExecCommand.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ConsoleExecCommand.java
@@ -28,7 +28,7 @@ public class ConsoleExecCommand extends AbstractFrameCommand<String> {
return new ResponseProcessor<String>() {
@Override
protected String parseResponse(ProtocolFrame response) throws PyDebuggerException {
- final PyDebugValue value = ProtocolParser.parseValue(response.getPayload(), myDebugger.getDebugProcess());
+ final PyDebugValue value = ProtocolParser.parseValue(response.getPayload(), getDebugger().getDebugProcess());
return value.getValue();
}
};
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetReferrersCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetReferrersCommand.java
new file mode 100644
index 000000000000..3698266f66b7
--- /dev/null
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetReferrersCommand.java
@@ -0,0 +1,50 @@
+package com.jetbrains.python.debugger.pydev;
+
+import com.jetbrains.python.debugger.PyDebugValue;
+import com.jetbrains.python.debugger.PyDebuggerException;
+import com.jetbrains.python.debugger.PyReferringObjectsValue;
+
+import java.util.List;
+
+/**
+ * @author traff
+ */
+public class GetReferrersCommand extends RunCustomOperationCommand<List<PyDebugValue>> {
+
+ public GetReferrersCommand(RemoteDebugger target, String threadId, String frameId, PyReferringObjectsValue value) {
+ super(target, createVariableLocator(threadId, frameId, value), "from pydevd_referrers import get_referrer_info",
+ "get_referrer_info");
+ }
+
+ @Override
+ protected ResponseProcessor<List<PyDebugValue>> createResponseProcessor() {
+ return new ResponseProcessor<List<PyDebugValue>>() {
+ @Override
+ protected List<PyDebugValue> parseResponse(ProtocolFrame response) throws PyDebuggerException {
+ return ProtocolParser.parseReferrers(decode(response.getPayload()), getDebugger().getDebugProcess());
+ }
+ };
+ }
+
+
+ private static PyVariableLocator createVariableLocator(final String threadId, final String frameId, final PyReferringObjectsValue var) {
+ return new PyVariableLocator() {
+ @Override
+ public String getThreadId() {
+ return threadId;
+ }
+
+
+ @Override
+ public String getPyDBLocation() {
+ if (var.getId() == null) {
+ return threadId + "\t" + frameId + "\tFRAME\t" + var.getName();
+ }
+ //Ok, this only happens when we're dealing with references with no proper scope given and we need to get
+ //things by id (which is usually not ideal). In this case we keep the proper thread id and set the frame id
+ //as the id of the object to be searched later on based on the list of all alive objects.
+ return getThreadId() + "\t" + var.getId() + "\tBY_ID";
+ }
+ };
+ }
+}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetVariableCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetVariableCommand.java
index ef4bd12059d7..e5a218607ac8 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetVariableCommand.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/GetVariableCommand.java
@@ -5,6 +5,7 @@ import com.jetbrains.python.debugger.PyDebugValue;
public class GetVariableCommand extends GetFrameCommand {
+ public static final String BY_ID = "BY_ID";
private final String myVariableName;
private final PyDebugValue myParent;
@@ -15,23 +16,40 @@ public class GetVariableCommand extends GetFrameCommand {
}
public static String composeName(final PyDebugValue var) {
- final StringBuilder sb = new StringBuilder(var.getTempName());
+ final StringBuilder sb = new StringBuilder();
PyDebugValue p = var;
- while ((p = p.getParent()) != null) {
- sb.insert(0, '\t').insert(0, p.getTempName());
+ while (p != null) {
+ if (sb.length() > 0 ) {
+ sb.insert(0, '\t');
+ }
+ if (p.getId() != null) {
+ sb.insert(0, BY_ID).insert(0, '\t').insert(0, p.getId());
+ break;
+ } else {
+ sb.insert(0, p.getTempName());
+ }
+ p = p.getParent();
}
return sb.toString();
}
@Override
protected void buildPayload(Payload payload) {
- super.buildPayload(payload);
- payload.add(myVariableName);
+ if (myParent.getVariableLocator() != null) {
+ payload.add(myParent.getVariableLocator().getThreadId()).add(myParent.getVariableLocator().getPyDBLocation());
+ }
+ else if (myVariableName.contains(BY_ID)) {
+ payload.add(getThreadId()).add(myVariableName);
+ }
+ else {
+ super.buildPayload(payload);
+ payload.add(myVariableName);
+ }
}
@Override
protected PyDebugValue extend(final PyDebugValue value) {
- return new PyDebugValue(value.getName(), value.getType(), value.getValue(), value.isContainer(), value.isErrorOnEval(), myParent, myDebugProcess);
+ return new PyDebugValue(value.getName(), value.getType(), value.getValue(), value.isContainer(), value.isErrorOnEval(), myParent,
+ myDebugProcess);
}
-
}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java
index d210d7ae03a8..d54bac8de85f 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java
@@ -8,10 +8,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.xdebugger.frame.XValueChildrenList;
import com.jetbrains.python.console.pydev.PydevCompletionVariant;
-import com.jetbrains.python.debugger.IPyDebugProcess;
-import com.jetbrains.python.debugger.PyDebugValue;
-import com.jetbrains.python.debugger.PyDebuggerException;
-import com.jetbrains.python.debugger.PyThreadInfo;
+import com.jetbrains.python.debugger.*;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@@ -168,7 +165,7 @@ public class MultiProcessDebugger implements ProcessDebugger {
}
@Override
- public void consoleExec(String threadId, String frameId, String expression, DebugCallback<String> callback) {
+ public void consoleExec(String threadId, String frameId, String expression, PyDebugCallback<String> callback) {
debugger(threadId).consoleExec(threadId, frameId, expression, callback);
}
@@ -182,6 +179,11 @@ public class MultiProcessDebugger implements ProcessDebugger {
return debugger(threadId).loadVariable(threadId, frameId, var);
}
+ @Override
+ public void loadReferrers(String threadId, String frameId, PyReferringObjectsValue var, PyDebugCallback<XValueChildrenList> callback) {
+ debugger(threadId).loadReferrers(threadId, frameId, var, callback);
+ }
+
@NotNull
private ProcessDebugger debugger(@NotNull String threadId) {
ProcessDebugger debugger = myThreadRegistry.getDebugger(threadId);
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java
index 2d864f0df2ca..0fa5a5c21931 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java
@@ -4,6 +4,7 @@ import com.intellij.xdebugger.frame.XValueChildrenList;
import com.jetbrains.python.console.pydev.PydevCompletionVariant;
import com.jetbrains.python.debugger.PyDebugValue;
import com.jetbrains.python.debugger.PyDebuggerException;
+import com.jetbrains.python.debugger.PyReferringObjectsValue;
import com.jetbrains.python.debugger.PyThreadInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -26,15 +27,17 @@ public interface ProcessDebugger {
String expression,
boolean execute,
boolean trimResult)
- throws PyDebuggerException;
+ throws PyDebuggerException;
- void consoleExec(String threadId, String frameId, String expression, DebugCallback<String> callback);
+ void consoleExec(String threadId, String frameId, String expression, PyDebugCallback<String> callback);
XValueChildrenList loadFrame(String threadId, String frameId) throws PyDebuggerException;
// todo: don't generate temp variables for qualified expressions - just split 'em
XValueChildrenList loadVariable(String threadId, String frameId, PyDebugValue var) throws PyDebuggerException;
+ void loadReferrers(String threadId, String frameId, PyReferringObjectsValue var, PyDebugCallback<XValueChildrenList> callback);
+
PyDebugValue changeVariable(String threadId, String frameId, PyDebugValue var, String value)
throws PyDebuggerException;
@@ -50,7 +53,7 @@ public interface ProcessDebugger {
void suspendThread(String threadId);
/**
- * Disconnects current debug process. Closes all resources.
+ * Disconnects current debug process. Closes all resources.
*/
void close();
@@ -84,12 +87,4 @@ public interface ProcessDebugger {
void addExceptionBreakpoint(ExceptionBreakpointCommandFactory factory);
void removeExceptionBreakpoint(ExceptionBreakpointCommandFactory factory);
-
- /**
- * @author traff
- */
- interface DebugCallback<T> {
- void ok(T value);
- void error(PyDebuggerException exception);
- }
}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java
index 0801077fbea3..1822b17efaa3 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProtocolParser.java
@@ -122,6 +122,32 @@ public class ProtocolParser {
}
@NotNull
+ public static List<PyDebugValue> parseReferrers(final String text, final PyFrameAccessor frameAccessor) throws PyDebuggerException {
+ final List<PyDebugValue> values = new LinkedList<PyDebugValue>();
+
+ final XppReader reader = openReader(text, false);
+
+ while (reader.hasMoreChildren()) {
+ reader.moveDown();
+ if (reader.getNodeName().equals("var")) {
+ PyDebugValue value = parseValue(reader, frameAccessor);
+ value.setId(readString(reader, "id", null));
+ values.add(value);
+ }
+ else if (reader.getNodeName().equals("for")) {
+ //TODO
+ }
+ else {
+ throw new PyDebuggerException("Expected <var> or <for>, found " + reader.getNodeName());
+ }
+ reader.moveUp();
+ }
+
+ return values;
+ }
+
+
+ @NotNull
public static List<PyDebugValue> parseValues(final String text, final PyFrameAccessor frameAccessor) throws PyDebuggerException {
final List<PyDebugValue> values = new LinkedList<PyDebugValue>();
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/PyDebugCallback.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/PyDebugCallback.java
new file mode 100644
index 000000000000..c78f3e20bdc5
--- /dev/null
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/PyDebugCallback.java
@@ -0,0 +1,12 @@
+package com.jetbrains.python.debugger.pydev;
+
+import com.jetbrains.python.debugger.PyDebuggerException;
+
+/**
+ * @author traff
+ */
+public interface PyDebugCallback<T> {
+ void ok(T value);
+
+ void error(PyDebuggerException exception);
+}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/PyVariableLocator.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/PyVariableLocator.java
new file mode 100644
index 000000000000..3ba4e1cd96de
--- /dev/null
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/PyVariableLocator.java
@@ -0,0 +1,17 @@
+package com.jetbrains.python.debugger.pydev;
+
+/**
+ * IVariableLocator knows how to produce location information
+ * for CMD_GET_VARIABLE
+ *
+ * The location is specified as:
+ *
+ * thread_id, stack_frame, LOCAL|GLOBAL, attribute*
+ */
+public interface PyVariableLocator {
+
+ public String getThreadId();
+
+ public String getPyDBLocation();
+
+}
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
index df33148edb10..4ce7bdf67434 100644
--- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java
@@ -133,7 +133,7 @@ public class RemoteDebugger implements ProcessDebugger {
}
@Override
- public void consoleExec(String threadId, String frameId, String expression, DebugCallback<String> callback) {
+ public void consoleExec(String threadId, String frameId, String expression, PyDebugCallback<String> callback) {
final ConsoleExecCommand command = new ConsoleExecCommand(this, threadId, frameId, expression);
command.execute(callback);
}
@@ -154,6 +154,31 @@ public class RemoteDebugger implements ProcessDebugger {
return command.getVariables();
}
+
+ @Override
+ public void loadReferrers(final String threadId,
+ final String frameId,
+ final PyReferringObjectsValue var,
+ final PyDebugCallback<XValueChildrenList> callback) {
+ RunCustomOperationCommand cmd = new GetReferrersCommand(this, threadId, frameId, var);
+
+ cmd.execute(new PyDebugCallback<List<PyDebugValue>>() {
+ @Override
+ public void ok(List<PyDebugValue> value) {
+ XValueChildrenList list = new XValueChildrenList();
+ for (PyDebugValue v : value) {
+ list.add(v);
+ }
+ callback.ok(list);
+ }
+
+ @Override
+ public void error(PyDebuggerException exception) {
+ callback.error(exception);
+ }
+ });
+ }
+
@Override
public PyDebugValue changeVariable(final String threadId, final String frameId, final PyDebugValue var, final String value)
throws PyDebuggerException {
@@ -191,7 +216,7 @@ public class RemoteDebugger implements ProcessDebugger {
// todo: change variable in lists doesn't work - either fix in pydevd or format var name appropriately
private void setTempVariable(final String threadId, final String frameId, final PyDebugValue var) {
final PyDebugValue topVar = var.getTopParent();
- if (myDebugProcess.isVariable(topVar.getName())) {
+ if (!myDebugProcess.canSaveToTemp(topVar.getName())) {
return;
}
if (myTempVars.contains(threadId, frameId, topVar.getTempName())) {
@@ -343,7 +368,7 @@ public class RemoteDebugger implements ProcessDebugger {
}
}
if (myDebuggerReader != null) {
- myDebuggerReader.close();
+ myDebuggerReader.stop();
}
fireCloseEvent();
}
@@ -428,7 +453,6 @@ public class RemoteDebugger implements ProcessDebugger {
}
private class DebuggerReader extends BaseOutputReader {
- private boolean myClosing = false;
private Reader myReader;
private DebuggerReader(final Reader reader) throws IOException {
@@ -442,7 +466,7 @@ public class RemoteDebugger implements ProcessDebugger {
while (true) {
boolean read = readAvailable();
- if (myClosing) {
+ if (isStopped) {
break;
}
@@ -453,7 +477,7 @@ public class RemoteDebugger implements ProcessDebugger {
fireCommunicationError();
}
finally {
- closeReader(myReader);
+ close();
fireExitEvent();
}
}
@@ -475,10 +499,10 @@ public class RemoteDebugger implements ProcessDebugger {
else if (AbstractCommand.isCallSignatureTrace(frame.getCommand())) {
recordCallSignature(ProtocolParser.parseCallSignature(frame.getPayload()));
}
- else if (AbstractCommand.isErrorEvent(frame.getCommand())) {
- LOG.error("Error response from debugger: " + frame.getPayload());
- }
else {
+ if (AbstractCommand.isErrorEvent(frame.getCommand())) {
+ LOG.error("Error response from debugger: " + frame.getPayload());
+ }
placeResponse(frame.getSequence(), frame);
}
}
@@ -568,7 +592,13 @@ public class RemoteDebugger implements ProcessDebugger {
}
public void close() {
- myClosing = true;
+ closeReader(myReader);
+ }
+
+ @Override
+ public void stop() {
+ super.stop();
+ close();
}
@Override
diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RunCustomOperationCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RunCustomOperationCommand.java
new file mode 100644
index 000000000000..7a3315b4acce
--- /dev/null
+++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RunCustomOperationCommand.java
@@ -0,0 +1,86 @@
+package com.jetbrains.python.debugger.pydev;
+
+import com.intellij.openapi.diagnostic.Logger;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+
+/**
+ * Run a custom bit of Python in the context of the specified debug target.
+ * <p>
+ * This command takes a variable or expression (expressed as an {@link PyVariableLocator#getPyDBLocation()} style
+ * location) and passes it to the function provided in the constructor. The constructor also takes either a code
+ * snippet that should define the function, or a file to execfile that should define the function.
+ * <p>
+ * Once created, the command should be posted to the target with {@link AbstractDebugTarget#postCommand(AbstractDebuggerCommand)}.
+ * Optionally, the function run on the target can return a string for further processing. In this case the command's
+ * {@link #setCompletionListener(ICommandResponseListener)} should be set and on completion, {@link #getResponsePayload()}
+ * can be used to obtain the returned value.
+ * <p>
+ * For an example, see {@link PrettyPrintCommandHandler}
+ */
+public class RunCustomOperationCommand<T> extends AbstractCommand<T> {
+ private static final Logger LOG = Logger.getInstance(RunCustomOperationCommand.class);
+
+ private String myEncodedCodeOrFile;
+ private String myOperationFnName;
+ private PyVariableLocator myLocator;
+ private String myStyle;
+
+ private RunCustomOperationCommand(RemoteDebugger target, PyVariableLocator locator,
+ String style, String codeOrFile, String operationFnName) {
+ super(target, CMD_RUN_CUSTOM_OPERATION);
+
+ this.myLocator = locator;
+ this.myStyle = style;
+ this.myEncodedCodeOrFile = encode(codeOrFile);
+ this.myOperationFnName = operationFnName;
+ }
+
+ /**
+ * Create a new command to run with the function defined in a string.
+ *
+ * @param target Debug Target to run on
+ * @param locator Location of variable or expression.
+ * @param operationSource Definition of the function to be run (this code is "exec"ed by the target)
+ * @param operationFnName Function to call, must be defined by operationSource
+ */
+ public RunCustomOperationCommand(RemoteDebugger target, PyVariableLocator locator,
+ String operationSource, String operationFnName) {
+ this(target, locator, "EXEC", operationSource, operationFnName);
+ }
+
+
+ @Override
+ protected void buildPayload(Payload payload) {
+ payload.add(myLocator.getPyDBLocation() + "||" + myStyle).add(myEncodedCodeOrFile).add(myOperationFnName);
+ }
+
+ @Override
+ public boolean isResponseExpected() {
+ return true;
+ }
+
+ private static String encode(String in) {
+ try {
+ return URLEncoder.encode(in, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ LOG.error("Unreachable? UTF-8 is always supported.", e);
+ return "";
+ }
+ }
+
+ protected static String decode(String in) {
+ try {
+ return URLDecoder.decode(in, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ LOG.error("Unreachable? UTF-8 is always supported.", e);
+ return "";
+ }
+ }
+
+}
+
diff --git a/python/python-rest/src/com/jetbrains/rest/RestPythonUtil.java b/python/python-rest/src/com/jetbrains/rest/RestPythonUtil.java
index f32fce8e40b7..e6d379f03676 100644
--- a/python/python-rest/src/com/jetbrains/rest/RestPythonUtil.java
+++ b/python/python-rest/src/com/jetbrains/rest/RestPythonUtil.java
@@ -26,7 +26,6 @@ import com.intellij.openapi.projectRoots.Sdk;
import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageManager;
-import com.jetbrains.python.packaging.PyPackageManagerImpl;
import com.jetbrains.python.sdk.PythonSdkType;
/**
@@ -48,9 +47,9 @@ public class RestPythonUtil {
if (module != null) {
Sdk sdk = PythonSdkType.findPythonSdk(module);
if (sdk != null) {
- PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk);
+ PyPackageManager manager = PyPackageManager.getInstance(sdk);
try {
- final PyPackage sphinx = manager.findInstalledPackage("Sphinx");
+ final PyPackage sphinx = manager.findPackage("Sphinx", false);
presentation.setEnabled(sphinx != null);
}
catch (PyExternalProcessException ignored) {
diff --git a/python/src/META-INF/pycharm-core.xml b/python/src/META-INF/pycharm-core.xml
index a9f7824b5340..ac42944c9953 100644
--- a/python/src/META-INF/pycharm-core.xml
+++ b/python/src/META-INF/pycharm-core.xml
@@ -40,10 +40,10 @@
<projectAttachProcessor implementation="com.intellij.platform.ModuleAttachProcessor"/>
- <projectConfigurable instance="com.jetbrains.python.configuration.PythonContentEntriesConfigurable"/>
- <projectConfigurable instance="com.jetbrains.python.buildout.BuildoutModulesConfigurable"/>
+ <projectConfigurable groupId="project" instance="com.jetbrains.python.configuration.PythonContentEntriesConfigurable"/>
+ <projectConfigurable groupId="build" instance="com.jetbrains.python.buildout.BuildoutModulesConfigurable"/>
<projectConfigurable groupId="project" instance="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"/>
- <projectConfigurable instance="com.jetbrains.python.configuration.PyDependenciesConfigurable"/>
+ <projectConfigurable groupId="project" instance="com.jetbrains.python.configuration.PyDependenciesConfigurable"/>
<directoryProjectConfigurator implementation="com.jetbrains.python.PythonSdkConfigurator" id="sdk"
order="after PlatformProjectConfigurator"/>
@@ -104,7 +104,7 @@
<add-to-group group-id="FileOpenGroup" anchor="after" relative-to-action="OpenFile"/>
</action>
- <action id="RerunFailedTests" class="com.intellij.execution.testframework.actions.AbstractRerunFailedTestsAction"
+ <action id="RerunFailedTests" class="com.intellij.execution.testframework.actions.RerunFailedTestsAction"
icon="AllIcons.RunConfigurations.RerunFailedTests"/>
<group id="WelcomeScreen.Platform.NewProject">
diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml
index 7adbc46c189c..5091d43386a1 100644
--- a/python/src/META-INF/python-core.xml
+++ b/python/src/META-INF/python-core.xml
@@ -137,6 +137,7 @@
<gotoTargetRendererProvider implementation="com.jetbrains.python.codeInsight.PyGotoTargetRendererProvider"/>
<typeHierarchyProvider language="Python" implementationClass="com.jetbrains.python.hierarchy.PyTypeHierachyProvider"/>
+ <callHierarchyProvider language="Python" implementationClass="com.jetbrains.python.hierarchy.call.PyCallHierarchyProvider"/>
<highlightUsagesHandlerFactory implementation="com.jetbrains.python.codeInsight.highlighting.PyHighlightExitPointsHandlerFactory"/>
<joinLinesHandler implementation="com.jetbrains.python.editor.PyJoinLinesHandler"/>
@@ -531,7 +532,8 @@
</extensions>
- <extensionPoints>
+ <extensionPoints>
+ <extensionPoint qualifiedName="Pythonid.pythonDocumentationQuickInfoProvider" interface="com.jetbrains.python.documentation.PythonDocumentationQuickInfoProvider"/>
<extensionPoint qualifiedName="Pythonid.importResolver" interface="com.jetbrains.python.psi.impl.PyImportResolver"/>
<extensionPoint qualifiedName="Pythonid.magicLiteral" interface="com.jetbrains.python.magicLiteral.PyMagicLiteralExtensionPoint"/>
<extensionPoint qualifiedName="Pythonid.unresolvedReferenceSkipper" interface="com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferenceSkipperExtPoint"/>
@@ -633,8 +635,6 @@
<separator/>
<reference ref="AddToFavorites"/>
<separator/>
- <reference ref="RunContextPopupGroup"/>
- <separator/>
<reference ref="ReformatCode"/>
<reference ref="OptimizeImports"/>
<reference ref="$Delete"/>
@@ -647,7 +647,26 @@
<reference ref="CompareFileWithEditor"/>
</group>
+ <group id="PyCallHierarchyPopupMenu">
+ <reference ref="EditSource"/>
+ <separator/>
+ <reference ref="FindUsages"/>
+ <reference ref="RefactoringMenu"/>
+ <separator/>
+ <reference ref="AddToFavorites"/>
+ <separator/>
+ <reference ref="ReformatCode"/>
+ <reference ref="OptimizeImports"/>
+ <separator/>
+ <reference ref="VersionControlsGroup"/>
+
+ <separator/>
+ <reference ref="ExternalToolsGroup"/>
+ <separator/>
+ <reference ref="CompareTwoFiles"/>
+ <reference ref="CompareFileWithEditor"/>
+ </group>
<action id="com.jetbrains.python.console.PyOpenDebugConsoleAction"
class="com.jetbrains.python.console.PyOpenDebugConsoleAction"
diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index 482daa88b66c..d82e9a6fde62 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -676,6 +676,9 @@ ANN.tuple.py3=tuple parameter unpacking is not supported in Python 3
ANN.star.import.at.top.only='import *' only allowed at module level
+ANN.missing.closing.quote=Missing closing quote [{0}]
+ANN.missing.closing.triple.quotes=Missing closing triple quotes
+
ANN.method.$0.removed.use.$1=Method ''{0}'' has been removed, use ''{1}'' instead
ANN.method.$0.removed=Method ''{0}'' removed
diff --git a/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java
index 2ecf3b54a8d0..ab13a59ff0b8 100644
--- a/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java
+++ b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java
@@ -278,7 +278,7 @@ public class PyKeywordCompletionContributor extends CompletionContributor {
private static final PsiElementPattern.Capture<PsiElement> IN_IF_BODY =
psiElement().inside(psiElement(PyStatementList.class).inside(psiElement(PyIfPart.class)));
- private static final PsiElementPattern.Capture<PsiElement> IN_LOOP =
+ private static final PsiElementPattern.Capture<PsiElement> IN_LOOP =
psiElement().inside(false, psiElement(PyLoopStatement.class), or(psiElement(PyFunction.class), psiElement(PyClass.class)));
// not exactly a beauty
@@ -645,6 +645,20 @@ public class PyKeywordCompletionContributor extends CompletionContributor {
new PyKeywordCompletionProvider(PyNames.FROM));
}
+ private void addYieldExpression() {
+ extend(CompletionType.BASIC,
+ psiElement()
+ .withLanguage(PythonLanguage.getInstance())
+ .andOr(psiElement()
+ .inside(false, psiElement(PyAssignmentStatement.class), psiElement(PyTargetExpression.class))
+ .afterLeaf(psiElement().withElementType(PyTokenTypes.EQ)),
+ psiElement()
+ .inside(false, psiElement(PyAugAssignmentStatement.class), psiElement(PyTargetExpression.class))
+ .afterLeaf(psiElement().withElementType(PyTokenTypes.AUG_ASSIGN_OPERATIONS)),
+ psiElement().inside(true, psiElement(PyParenthesizedExpression.class))),
+ new PyKeywordCompletionProvider(PyNames.YIELD));
+ }
+
private void addYieldFrom() {
extend(CompletionType.BASIC,
psiElement()
@@ -671,6 +685,7 @@ public class PyKeywordCompletionContributor extends CompletionContributor {
//addExprIf();
addExprElse();
addRaiseFrom();
+ addYieldExpression();
addYieldFrom();
addForToComprehensions();
addInToFor();
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java b/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java
index 65cd5e1a79ce..89b6d9bc4a5d 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java
@@ -108,7 +108,6 @@ public class PyStatementMover extends LineMover {
if (moveOutsideFile(document, lineNumber)) return null;
int lineEndOffset = document.getLineEndOffset(lineNumber);
final int startOffset = document.getLineStartOffset(lineNumber);
- lineEndOffset = startOffset != lineEndOffset ? lineEndOffset - 1 : lineEndOffset;
final PyStatementList statementList = getStatementList(elementToMove);
@@ -119,10 +118,6 @@ public class PyStatementMover extends LineMover {
final int startLine = document.getLineNumber(start);
final int endLine = document.getLineNumber(end);
- if (elementToMove instanceof PsiComment && destination instanceof PsiComment) {
- return new LineRange(lineNumber, lineNumber + 1);
- }
-
if (elementToMove instanceof PyClass || elementToMove instanceof PyFunction) {
PyElement scope = statementList == null ? (PyElement)elementToMove.getContainingFile() : statementList;
if (destination != null)
@@ -137,6 +132,11 @@ public class PyStatementMover extends LineMover {
scopeRange = moveInto(elementToMove, file, editor, down, lineEndOffset);
if (scopeRange != null) return scopeRange;
+ if (elementToMove instanceof PsiComment && ( PsiTreeUtil.isAncestor(destination, elementToMove, true)) ||
+ destination instanceof PsiComment) {
+ return new LineRange(lineNumber, lineNumber + 1);
+ }
+
final PyElement scope = statementList == null ? (PyElement)elementToMove.getContainingFile() : statementList;
if ((elementToMove instanceof PyClass) || (elementToMove instanceof PyFunction))
return new ScopeRange(scope, scope.getFirstChild(), !down, true);
@@ -185,7 +185,6 @@ public class PyStatementMover extends LineMover {
if (sibling != null) {
final PyStatementList list = sibling.getStatementList();
- assert list != null;
return new ScopeRange(list, down ? list.getFirstChild() : list.getLastChild(), !addBefore);
}
else {
@@ -278,11 +277,23 @@ public class PyStatementMover extends LineMover {
private static PsiElement getDestinationElement(@NotNull final PsiElement elementToMove, @NotNull final Document document,
int lineEndOffset, boolean down) {
- PsiElement destination = elementToMove.getContainingFile().findElementAt(lineEndOffset);
- if (destination == null) return null;
- if (destination instanceof PsiComment) return destination;
+ PsiElement destination = PyUtil.findPrevAtOffset(elementToMove.getContainingFile(), lineEndOffset, PsiWhiteSpace.class);
PsiElement sibling = down ? PsiTreeUtil.getNextSiblingOfType(elementToMove, PyStatement.class) :
- PsiTreeUtil.getPrevSiblingOfType(elementToMove, PyStatement.class);
+ PsiTreeUtil.getPrevSiblingOfType(elementToMove, PyStatement.class);
+ if (destination == null) {
+ if (elementToMove instanceof PyClass) {
+ destination = sibling;
+ }
+ else if (elementToMove instanceof PyFunction) {
+ if (!(sibling instanceof PyClass))
+ destination = sibling;
+ else destination = null;
+ }
+ else {
+ return null;
+ }
+ }
+ if (destination instanceof PsiComment) return destination;
if (elementToMove instanceof PyClass) {
destination = sibling;
}
diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java
index ec236d9242f5..b2b71cb9e0d2 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/smartEnter/fixers/PyWithFixer.java
@@ -18,6 +18,7 @@ package com.jetbrains.python.codeInsight.editorActions.smartEnter.fixers;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiElement;
+import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.codeInsight.editorActions.smartEnter.PySmartEnterProcessor;
@@ -27,8 +28,6 @@ import com.jetbrains.python.psi.PyWithItem;
import com.jetbrains.python.psi.PyWithStatement;
import org.jetbrains.annotations.NotNull;
-import static com.jetbrains.python.psi.PyUtil.sure;
-
/**
* @author Mikhail Golubev
*/
@@ -42,11 +41,10 @@ public class PyWithFixer extends PyFixer<PyWithStatement> {
final PsiElement colonToken = PyUtil.getFirstChildOfType(withStatement, PyTokenTypes.COLON);
final PsiElement withToken = PyUtil.getFirstChildOfType(withStatement, PyTokenTypes.WITH_KEYWORD);
final Document document = editor.getDocument();
- if (colonToken == null) {
- int insertAt = sure(withToken).getTextRange().getEndOffset();
+ if (colonToken == null && withToken != null) {
+ int insertAt = withToken.getTextRange().getEndOffset();
String textToInsert = ":";
- final PyWithItem[] withItems = withStatement.getWithItems();
- final PyWithItem lastItem = withItems.length != 0 ? withItems[withItems.length - 1] : null;
+ final PyWithItem lastItem = ArrayUtil.getLastElement(withStatement.getWithItems());
if (lastItem == null || lastItem.getExpression() == null) {
textToInsert = " :";
processor.registerUnresolvedError(insertAt + 1);
diff --git a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java
index fa869bfca359..e9585a5cd952 100644
--- a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java
+++ b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java
@@ -156,7 +156,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable, No
return new FacetConfigurationQuickFix() {
@Override
public void run(JComponent place) {
- final PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, sdk, new PyPackageManagerImpl.UI.Listener() {
+ final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, sdk, new PyPackageManagerUI.Listener() {
@Override
public void started() {}
diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java
index 9c95b1cffda8..5bf06782d376 100644
--- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java
+++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java
@@ -31,10 +31,7 @@ import com.intellij.util.Function;
import com.intellij.xdebugger.frame.XValueChildrenList;
import com.jetbrains.python.console.parsing.PythonConsoleData;
import com.jetbrains.python.console.pydev.*;
-import com.jetbrains.python.debugger.PyDebugValue;
-import com.jetbrains.python.debugger.PyDebuggerException;
-import com.jetbrains.python.debugger.PyFrameAccessor;
-import com.jetbrains.python.debugger.PydevXmlUtils;
+import com.jetbrains.python.debugger.*;
import com.jetbrains.python.debugger.pydev.GetVariableCommand;
import com.jetbrains.python.debugger.pydev.ProtocolParser;
import org.apache.xmlrpc.WebServer;
@@ -528,6 +525,11 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl
}
}
+ @Nullable
+ @Override
+ public PyReferrersLoader getReferrersLoader() {
+ return null;
+ }
/**
* Request that pydevconsole connect (with pydevd) to the specified port
diff --git a/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java b/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java
index da1ac53113ef..5f361b30228a 100644
--- a/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java
+++ b/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java
@@ -23,7 +23,7 @@ import com.jetbrains.python.console.pydev.InterpreterResponse;
import com.jetbrains.python.console.pydev.PydevCompletionVariant;
import com.jetbrains.python.debugger.PyDebugProcess;
import com.jetbrains.python.debugger.PyDebuggerException;
-import com.jetbrains.python.debugger.pydev.ProcessDebugger;
+import com.jetbrains.python.debugger.pydev.PyDebugCallback;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -63,8 +63,8 @@ public class PythonDebugConsoleCommunication extends AbstractConsoleCommunicatio
return false;
}
- protected void exec(final ConsoleCodeFragment command, final ProcessDebugger.DebugCallback<Pair<String, Boolean>> callback) {
- myDebugProcess.consoleExec(command.getText(), new ProcessDebugger.DebugCallback<String>() {
+ protected void exec(final ConsoleCodeFragment command, final PyDebugCallback<Pair<String, Boolean>> callback) {
+ myDebugProcess.consoleExec(command.getText(), new PyDebugCallback<String>() {
@Override
public void ok(String value) {
callback.ok(parseExecResponseString(value));
@@ -79,7 +79,7 @@ public class PythonDebugConsoleCommunication extends AbstractConsoleCommunicatio
public void execInterpreter(ConsoleCodeFragment code, final Function<InterpreterResponse, Object> callback) {
myExpression.append(code.getText());
- exec(new ConsoleCodeFragment(myExpression.toString(), false), new ProcessDebugger.DebugCallback<Pair<String, Boolean>>() {
+ exec(new ConsoleCodeFragment(myExpression.toString(), false), new PyDebugCallback<Pair<String, Boolean>>() {
@Override
public void ok(Pair<String, Boolean> executed) {
boolean more = executed.second;
diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
index cdea41298a4e..ea492e43cf2d 100644
--- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
+++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java
@@ -17,7 +17,6 @@ package com.jetbrains.python.debugger;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
-import com.intellij.execution.console.DuplexConsoleView;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.process.ProcessListener;
@@ -88,6 +87,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
private boolean myWaitingForConnection = false;
private PyStackFrame myStackFrameBeforeResume;
private PyStackFrame myConsoleContextFrame = null;
+ private PyReferrersLoader myReferrersProvider;
public PyDebugProcess(final @NotNull XDebugSession session,
@NotNull final ServerSocket serverSocket,
@@ -489,7 +489,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
return myDebugger.evaluate(frame.getThreadId(), frame.getFrameId(), expression, execute, trimResult);
}
- public void consoleExec(String command, ProcessDebugger.DebugCallback<String> callback) {
+ public void consoleExec(String command, PyDebugCallback<String> callback) {
dropFrameCaches();
try {
final PyStackFrame frame = currentFrame();
@@ -539,6 +539,17 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
}
@Override
+ public void loadReferrers(PyReferringObjectsValue var, PyDebugCallback<XValueChildrenList> callback) {
+ try {
+ final PyStackFrame frame = currentFrame();
+ myDebugger.loadReferrers(frame.getThreadId(), frame.getFrameId(), var, callback);
+ }
+ catch (PyDebuggerException e) {
+ callback.error(e);
+ }
+ }
+
+ @Override
public void changeVariable(final PyDebugValue var, final String value) throws PyDebuggerException {
final PyStackFrame frame = currentFrame();
PyDebugValue newValue = myDebugger.changeVariable(frame.getThreadId(), frame.getFrameId(), var, value);
@@ -546,14 +557,23 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
}
@Nullable
+ @Override
+ public PyReferrersLoader getReferrersLoader() {
+ if (myReferrersProvider == null) {
+ myReferrersProvider = new PyReferrersLoader(this);
+ }
+ return myReferrersProvider;
+ }
+
+ @Nullable
public String loadSource(String path) {
return myDebugger.loadSource(path);
}
@Override
- public boolean isVariable(String name) {
+ public boolean canSaveToTemp(String name) {
final Project project = getSession().getProject();
- return PyDebugSupportUtils.isVariable(project, name);
+ return PyDebugSupportUtils.canSaveToTemp(project, name);
}
private PyStackFrame currentFrame() throws PyDebuggerException {
diff --git a/python/src/com/jetbrains/python/debugger/PyDebugSupportUtils.java b/python/src/com/jetbrains/python/debugger/PyDebugSupportUtils.java
index d29259d559c6..d25596a13f2a 100644
--- a/python/src/com/jetbrains/python/debugger/PyDebugSupportUtils.java
+++ b/python/src/com/jetbrains/python/debugger/PyDebugSupportUtils.java
@@ -80,25 +80,29 @@ public class PyDebugSupportUtils {
element instanceof PyNamedParameter;
}
- // is expression a variable reference
+ // is expression a variable reference and can be evaluated
// todo: use patterns (?)
- public static boolean isVariable(final Project project, final String expression) {
+ public static boolean canSaveToTemp(final Project project, final String expression) {
return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
public Boolean compute() {
final PsiFile file = PyElementGenerator.getInstance(project).createDummyFile(LanguageLevel.getDefault(), expression);
final PsiElement root = file.getFirstChild();
- return root instanceof PyExpressionStatement &&
- root.getFirstChild() instanceof PyReferenceExpression &&
- root.getFirstChild() == root.getLastChild() &&
- root.getFirstChild().getFirstChild() != null &&
- root.getFirstChild().getFirstChild().getNode().getElementType() == PyTokenTypes.IDENTIFIER &&
- root.getFirstChild().getFirstChild() == root.getFirstChild().getLastChild() &&
- root.getFirstChild().getFirstChild().getFirstChild() == null;
+ return !isVariable(root) && (root instanceof PyExpressionStatement);
}
});
}
+ private static Boolean isVariable(PsiElement root) {
+ return root instanceof PyExpressionStatement &&
+ root.getFirstChild() instanceof PyReferenceExpression &&
+ root.getFirstChild() == root.getLastChild() &&
+ root.getFirstChild().getFirstChild() != null &&
+ root.getFirstChild().getFirstChild().getNode().getElementType() == PyTokenTypes.IDENTIFIER &&
+ root.getFirstChild().getFirstChild() == root.getFirstChild().getLastChild() &&
+ root.getFirstChild().getFirstChild().getFirstChild() == null;
+ }
+
@Nullable
private static String getLineText(@NotNull Document document, int line) {
if (line > 0 && line < document.getLineCount()) {
diff --git a/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointProperties.java b/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointProperties.java
index d6bfd13f488c..93f9d89e386c 100644
--- a/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointProperties.java
+++ b/python/src/com/jetbrains/python/debugger/PyExceptionBreakpointProperties.java
@@ -39,6 +39,7 @@ public class PyExceptionBreakpointProperties extends ExceptionBreakpointProperti
public PyExceptionBreakpointProperties(@NotNull final String exception) {
myException = exception;
+ myNotifyOnTerminate = true;
}
@Override
@@ -78,6 +79,10 @@ public class PyExceptionBreakpointProperties extends ExceptionBreakpointProperti
myNotifyOnlyOnFirst = notifyOnlyOnFirst;
}
+ public String getException() {
+ return "python-" + myException;
+ }
+
@Override
public ExceptionBreakpointCommand createAddCommand(RemoteDebugger debugger) {
return ExceptionBreakpointCommand.addExceptionBreakpointCommand(debugger, getException(),
diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java
index 913ed65f1e53..73a0cb604091 100644
--- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java
+++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java
@@ -81,7 +81,16 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
@NonNls private static final String EPYDOC_PREFIX = "@";
// provides ctrl+hover info
- public String getQuickNavigateInfo(final PsiElement element, PsiElement originalElement) {
+ @Override
+ @Nullable
+ public String getQuickNavigateInfo(final PsiElement element, final PsiElement originalElement) {
+ for (final PythonDocumentationQuickInfoProvider point : PythonDocumentationQuickInfoProvider.EP_NAME.getExtensions()) {
+ String info = point.getQuickInfo(originalElement);
+ if (info != null) {
+ return info;
+ }
+ }
+
if (element instanceof PyFunction) {
PyFunction func = (PyFunction)element;
StringBuilder cat = new StringBuilder();
@@ -637,8 +646,9 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
String raiseTarget = visitor.myRaiseTarget.getText();
if (visitor.myRaiseTarget instanceof PyCallExpression) {
final PyExpression callee = ((PyCallExpression)visitor.myRaiseTarget).getCallee();
- if (callee != null)
+ if (callee != null) {
raiseTarget = callee.getText();
+ }
}
builder.append(" ").append(raiseTarget);
}
diff --git a/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java
index 8c96916c9d80..fa7cf1b429c4 100644
--- a/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java
+++ b/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java
@@ -27,12 +27,12 @@ import java.util.List;
public class PyFunctionFindUsagesHandler extends FindUsagesHandler {
private final List<PsiElement> myAllElements;
- protected PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) {
+ public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement) {
super(psiElement);
myAllElements = null;
}
- protected PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement, List<PsiElement> allElements) {
+ public PyFunctionFindUsagesHandler(@NotNull PsiElement psiElement, List<PsiElement> allElements) {
super(psiElement);
myAllElements = allElements;
}
diff --git a/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyNodeDescriptor.java b/python/src/com/jetbrains/python/hierarchy/PyHierarchyNodeDescriptor.java
index ea8c250b2e4f..c9720fb01af3 100644
--- a/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyNodeDescriptor.java
+++ b/python/src/com/jetbrains/python/hierarchy/PyHierarchyNodeDescriptor.java
@@ -19,15 +19,14 @@ import com.intellij.ide.IdeBundle;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.navigation.ItemPresentation;
-import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.roots.ui.util.CompositeAppearance;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.NavigatablePsiElement;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyFunction;
import org.jetbrains.annotations.NotNull;
-
-import java.awt.*;
+import org.jetbrains.annotations.Nullable;
/**
* Created by IntelliJ IDEA.
@@ -35,14 +34,14 @@ import java.awt.*;
* Date: Jul 31, 2009
* Time: 6:26:37 PM
*/
-public class PyTypeHierarchyNodeDescriptor extends HierarchyNodeDescriptor {
-
- public PyTypeHierarchyNodeDescriptor(final NodeDescriptor parentDescriptor, @NotNull final PsiElement element, final boolean isBase) {
+public class PyHierarchyNodeDescriptor extends HierarchyNodeDescriptor {
+ public PyHierarchyNodeDescriptor(final NodeDescriptor parentDescriptor, @NotNull final PsiElement element, final boolean isBase) {
super(element.getProject(), parentDescriptor, element, isBase);
}
- public PyClass getClassElement() {
- return (PyClass)myElement;
+ @Nullable
+ public PsiElement getPsiElement() {
+ return myElement;
}
public boolean isValid() {
@@ -55,10 +54,6 @@ public class PyTypeHierarchyNodeDescriptor extends HierarchyNodeDescriptor {
final CompositeAppearance oldText = myHighlightedText;
myHighlightedText = new CompositeAppearance();
- TextAttributes classNameAttributes = null;
- if (myColor != null) {
- classNameAttributes = new TextAttributes(myColor, null, null, null, Font.PLAIN);
- }
NavigatablePsiElement element = (NavigatablePsiElement)myElement;
if (element == null) {
@@ -71,10 +66,14 @@ public class PyTypeHierarchyNodeDescriptor extends HierarchyNodeDescriptor {
final ItemPresentation presentation = element.getPresentation();
if (presentation != null) {
- final PyClass cl = getClassElement();
- myHighlightedText.getEnding().addText(cl.getName(), classNameAttributes);
- myHighlightedText.getEnding()
- .addText(" (" + cl.getContainingFile().getName() + ")", HierarchyNodeDescriptor.getPackageNameAttributes());
+ if (element instanceof PyFunction) {
+ final PyClass cls = ((PyFunction)element).getContainingClass();
+ if (cls != null) {
+ myHighlightedText.getEnding().addText(cls.getName() + ".");
+ }
+ }
+ myHighlightedText.getEnding().addText(presentation.getPresentableText());
+ myHighlightedText.getEnding().addText(" " + presentation.getLocationString(), HierarchyNodeDescriptor.getPackageNameAttributes());
}
myName = myHighlightedText.getText();
diff --git a/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyBrowser.java b/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyBrowser.java
index 643440571618..817d462044fe 100644
--- a/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyBrowser.java
+++ b/python/src/com/jetbrains/python/hierarchy/PyTypeHierarchyBrowser.java
@@ -52,10 +52,10 @@ public class PyTypeHierarchyBrowser extends TypeHierarchyBrowserBase {
@Nullable
protected PsiElement getElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) {
- if (!(descriptor instanceof PyTypeHierarchyNodeDescriptor)) {
+ if (!(descriptor instanceof PyHierarchyNodeDescriptor)) {
return null;
}
- return ((PyTypeHierarchyNodeDescriptor)descriptor).getClassElement();
+ return ((PyHierarchyNodeDescriptor)descriptor).getPsiElement();
}
protected void createTrees(@NotNull Map<String, JTree> trees) {
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java
new file mode 100644
index 000000000000..c5224e617ec9
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyBrowser.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.hierarchy.call;
+
+import com.intellij.ide.hierarchy.CallHierarchyBrowserBase;
+import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
+import com.intellij.ide.hierarchy.HierarchyTreeStructure;
+import com.intellij.ide.util.treeView.NodeDescriptor;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.psi.PsiElement;
+import com.intellij.ui.PopupHandler;
+import com.jetbrains.python.hierarchy.PyHierarchyNodeDescriptor;
+import com.jetbrains.python.hierarchy.PyHierarchyUtils;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyFunction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.Comparator;
+import java.util.Map;
+
+/**
+ * @author novokrest
+ */
+public class PyCallHierarchyBrowser extends CallHierarchyBrowserBase {
+ private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.hierarchy.call.PyCallHierarchyBrowser");
+ private static final String GROUP_PY_CALL_HIERARCHY_POPUP = "PyCallHierarchyPopupMenu";
+
+ public PyCallHierarchyBrowser(PsiElement function) {
+ super(function.getProject(), function);
+ }
+
+ @Nullable
+ @Override
+ protected PsiElement getElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) {
+ if (descriptor instanceof PyHierarchyNodeDescriptor) {
+ PyHierarchyNodeDescriptor pyDescriptor = (PyHierarchyNodeDescriptor)descriptor;
+ return pyDescriptor.getPsiElement();
+ }
+ return null;
+ }
+
+ @Override
+ protected void createTrees(@NotNull Map<String, JTree> type2TreeMap) {
+ final ActionGroup group = (ActionGroup)ActionManager.getInstance().getAction(GROUP_PY_CALL_HIERARCHY_POPUP);
+
+ final JTree callerTree = createHierarchyTree(group);
+ final JTree calleeTree = createHierarchyTree(group);
+
+ type2TreeMap.put(CALLER_TYPE, callerTree);
+ type2TreeMap.put(CALLEE_TYPE, calleeTree);
+ }
+
+ private JTree createHierarchyTree(ActionGroup group) {
+ final JTree tree = createTree(false);
+ PopupHandler.installPopupHandler(tree, group, ActionPlaces.CALL_HIERARCHY_VIEW_POPUP, ActionManager.getInstance());
+ return tree;
+ }
+
+ @Override
+ protected boolean isApplicableElement(@NotNull PsiElement element) {
+ return element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile;
+ }
+
+ @Nullable
+ @Override
+ protected HierarchyTreeStructure createHierarchyTreeStructure(@NotNull String typeName, @NotNull PsiElement psiElement) {
+ if (CALLER_TYPE.equals(typeName)) {
+ return new PyCallerFunctionTreeStructure(myProject, psiElement, getCurrentScopeType());
+ }
+ else if (CALLEE_TYPE.equals(typeName)) {
+ return new PyCalleeFunctionTreeStructure(myProject, psiElement, getCurrentScopeType());
+ }
+ else {
+ LOG.error("unexpected type: " + typeName);
+ return null;
+ }
+ }
+
+ @Nullable
+ @Override
+ protected Comparator<NodeDescriptor> getComparator() {
+ return PyHierarchyUtils.getComparator(myProject);
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java
new file mode 100644
index 000000000000..67c0d6ddfa0b
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.hierarchy.call;
+
+import com.intellij.codeInsight.TargetElementUtilBase;
+import com.intellij.ide.hierarchy.CallHierarchyBrowserBase;
+import com.intellij.ide.hierarchy.HierarchyBrowser;
+import com.intellij.ide.hierarchy.HierarchyProvider;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyFunction;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author novokrest
+ */
+public class PyCallHierarchyProvider implements HierarchyProvider {
+ @Nullable
+ @Override
+ public PsiElement getTarget(@NotNull DataContext dataContext) {
+ Project project = CommonDataKeys.PROJECT.getData(dataContext);
+ if (project == null) return null;
+
+ PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
+ if (element == null) {
+ Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
+ if (editor != null) {
+ PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
+ if (file == null) return null;
+
+ element = TargetElementUtilBase.findTargetElement(editor, TargetElementUtilBase.ELEMENT_NAME_ACCEPTED |
+ TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED |
+ TargetElementUtilBase.LOOKUP_ITEM_ACCEPTED);
+ if (element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile) {
+ return element;
+ }
+
+ element = file.findElementAt(editor.getCaretModel().getOffset());
+ }
+ }
+ return PsiTreeUtil.getNonStrictParentOfType(element, PyFunction.class, PyClass.class, PyFile.class);
+ }
+
+ @NotNull
+ @Override
+ public HierarchyBrowser createHierarchyBrowser(PsiElement target) {
+ return new PyCallHierarchyBrowser(target);
+ }
+
+ @Override
+ public void browserActivated(@NotNull HierarchyBrowser hierarchyBrowser) {
+ ((PyCallHierarchyBrowser)hierarchyBrowser).changeView(CallHierarchyBrowserBase.CALLER_TYPE);
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyTreeStructureBase.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyTreeStructureBase.java
new file mode 100644
index 000000000000..c5d60138299c
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallHierarchyTreeStructureBase.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.hierarchy.call;
+
+import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
+import com.intellij.ide.hierarchy.HierarchyTreeStructure;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.ArrayUtil;
+import com.jetbrains.python.hierarchy.PyHierarchyNodeDescriptor;
+import com.jetbrains.python.psi.PyClass;
+import com.jetbrains.python.psi.PyElement;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyFunction;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @author novokrest
+ */
+public abstract class PyCallHierarchyTreeStructureBase extends HierarchyTreeStructure {
+ private final String myScopeType;
+
+ public PyCallHierarchyTreeStructureBase(Project project, PsiElement element, String currentScopeType) {
+ super(project, new PyHierarchyNodeDescriptor(null, element, true));
+ myScopeType = currentScopeType;
+ }
+
+ @NotNull
+ protected abstract List<PsiElement> getChildren(@NotNull PyElement element);
+
+ @NotNull
+ @Override
+ protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) {
+ final List<PyHierarchyNodeDescriptor> descriptors = new ArrayList<PyHierarchyNodeDescriptor>();
+ if (descriptor instanceof PyHierarchyNodeDescriptor) {
+ final PyHierarchyNodeDescriptor pyDescriptor = (PyHierarchyNodeDescriptor)descriptor;
+ final PsiElement element = pyDescriptor.getPsiElement();
+ final boolean isCallable = element instanceof PyFunction || element instanceof PyClass || element instanceof PyFile;
+ HierarchyNodeDescriptor nodeDescriptor = getBaseDescriptor();
+ if (!(element instanceof PyElement) || !isCallable || nodeDescriptor == null) {
+ return ArrayUtil.EMPTY_OBJECT_ARRAY;
+ }
+
+ final List<PsiElement> children = getChildren((PyElement)element);
+
+ final HashMap<PsiElement, PyHierarchyNodeDescriptor> callerToDescriptorMap = new HashMap<PsiElement, PyHierarchyNodeDescriptor>();
+ PsiElement baseClass = element instanceof PyFunction ? ((PyFunction)element).getContainingClass() : null;
+
+ for (PsiElement caller : children) {
+ if (isInScope(baseClass, caller, myScopeType)) {
+ PyHierarchyNodeDescriptor callerDescriptor = callerToDescriptorMap.get(caller);
+ if (callerDescriptor == null) {
+ callerDescriptor = new PyHierarchyNodeDescriptor(descriptor, caller, false);
+ callerToDescriptorMap.put(caller, callerDescriptor);
+ descriptors.add(callerDescriptor);
+ }
+ }
+ }
+
+ }
+ return ArrayUtil.toObjectArray(descriptors);
+ }
+
+ @Override
+ public boolean isAlwaysShowPlus() {
+ return true;
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java
new file mode 100644
index 000000000000..b0465083ef05
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCalleeFunctionTreeStructure.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.hierarchy.call;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.psi.PyElement;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author novokrest
+ */
+public class PyCalleeFunctionTreeStructure extends PyCallHierarchyTreeStructureBase {
+ public PyCalleeFunctionTreeStructure(Project project, PsiElement element, String currentScopeType) {
+ super(project, element, currentScopeType);
+ }
+
+ @NotNull
+ @Override
+ protected List<PsiElement> getChildren(@NotNull PyElement element) {
+ final List<PsiElement> callees = new ArrayList<PsiElement>();
+ // TODO: Add callees from the dynamic call data manager
+ callees.addAll(PyStaticCallHierarchyUtil.getCallees(element));
+ return callees;
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java
new file mode 100644
index 000000000000..d45e79ce8d95
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyCallerFunctionTreeStructure.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.hierarchy.call;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.psi.PyElement;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author novokrest
+ */
+public class PyCallerFunctionTreeStructure extends PyCallHierarchyTreeStructureBase {
+ public PyCallerFunctionTreeStructure(Project project, PsiElement element, String currentScopeType) {
+ super(project, element, currentScopeType);
+ }
+
+ @NotNull
+ @Override
+ protected List<PsiElement> getChildren(@NotNull PyElement element) {
+ final List<PsiElement> callers = new ArrayList<PsiElement>();
+ // TODO: Add callers from the dynamic call data manager
+ callers.addAll(PyStaticCallHierarchyUtil.getCallers(element));
+ return callers;
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java
new file mode 100644
index 000000000000..5657dcb589e9
--- /dev/null
+++ b/python/src/com/jetbrains/python/hierarchy/call/PyStaticCallHierarchyUtil.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.hierarchy.call;
+
+import com.google.common.collect.Lists;
+import com.intellij.find.findUsages.FindUsagesHandler;
+import com.intellij.find.findUsages.FindUsagesOptions;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.usageView.UsageInfo;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.CommonProcessors;
+import com.jetbrains.python.PyNames;
+import com.jetbrains.python.findUsages.PyClassFindUsagesHandler;
+import com.jetbrains.python.findUsages.PyFunctionFindUsagesHandler;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.impl.PyBuiltinCache;
+import com.jetbrains.python.psi.resolve.PyResolveContext;
+import com.jetbrains.python.psi.search.PySuperMethodsSearch;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author novokrest
+ */
+public class PyStaticCallHierarchyUtil {
+ public static Collection<PsiElement> getCallees(@NotNull PyElement element) {
+ final List<PsiElement> callees = Lists.newArrayList();
+
+ final PyRecursiveElementVisitor visitor = new PyRecursiveElementVisitor() {
+ @Override
+ public void visitPyParameterList(PyParameterList node) {
+ }
+
+ @Override
+ public void visitPyLambdaExpression(PyLambdaExpression node) {
+ }
+
+ @Override
+ public void visitPyFunction(PyFunction innerFunction) {
+ for (PyParameter parameter : innerFunction.getParameterList().getParameters()) {
+ PsiElement defaultValue = parameter.getDefaultValue();
+ if (defaultValue != null) {
+ defaultValue.accept(this);
+ }
+ }
+ }
+
+ @Override
+ public void visitPyCallExpression(PyCallExpression callExpression) {
+ super.visitPyCallExpression(callExpression);
+ PsiElement calleeFunction = callExpression.resolveCalleeFunction(PyResolveContext.defaultContext());
+ if (calleeFunction instanceof PyFunction) {
+ callees.add(calleeFunction);
+ }
+ }
+ };
+
+ visitor.visitElement(element);
+
+ return callees;
+ }
+
+ public static Collection<PsiElement> getCallers(@NotNull PyElement pyElement) {
+ final List<PsiElement> callers = Lists.newArrayList();
+ final Collection<UsageInfo> usages = findUsages(pyElement);
+
+ for (UsageInfo usage : usages) {
+ PsiElement element = usage.getElement();
+ if (element == null) {
+ continue;
+ }
+
+ element = element.getParent();
+ while (element instanceof PyParenthesizedExpression) {
+ element = element.getParent();
+ }
+
+ if (element instanceof PyCallExpression) {
+ PsiElement caller = PsiTreeUtil.getParentOfType(element, PyParameterList.class, PyFunction.class);
+ if (caller instanceof PyFunction) {
+ callers.add(caller);
+ }
+ else if (caller instanceof PyParameterList) {
+ PsiElement innerFunction = PsiTreeUtil.getParentOfType(caller, PyFunction.class);
+ PsiElement outerFunction = PsiTreeUtil.getParentOfType(innerFunction, PyFunction.class);
+ if (innerFunction != null && outerFunction != null) {
+ callers.add(outerFunction);
+ }
+ }
+ }
+ }
+
+ return callers;
+ }
+
+ private static Collection<UsageInfo> findUsages(@NotNull final PsiElement element) {
+ final FindUsagesHandler handler = createFindUsageHandler(element);
+ if (handler == null) {
+ return Lists.newArrayList();
+ }
+ final CommonProcessors.CollectProcessor<UsageInfo> processor = new CommonProcessors.CollectProcessor<UsageInfo>();
+ final PsiElement[] psiElements = ArrayUtil.mergeArrays(handler.getPrimaryElements(), handler.getSecondaryElements());
+ final FindUsagesOptions options = handler.getFindUsagesOptions(null);
+ for (PsiElement psiElement : psiElements) {
+ handler.processElementUsages(psiElement, processor, options);
+ }
+ return processor.getResults();
+ }
+
+ /**
+ * @see {@link com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory#createFindUsagesHandler(com.intellij.psi.PsiElement, boolean) createFindUsagesHandler}
+ */
+ @Nullable
+ private static FindUsagesHandler createFindUsageHandler(@NotNull final PsiElement element) {
+ if (element instanceof PyFunction) {
+ final Collection<PsiElement> superMethods = PySuperMethodsSearch.search((PyFunction)element, true).findAll();
+ if (superMethods.size() > 0) {
+ final PsiElement next = superMethods.iterator().next();
+ if (next instanceof PyFunction && !isInObject((PyFunction)next)) {
+ List<PsiElement> allMethods = Lists.newArrayList();
+ allMethods.add(element);
+ allMethods.addAll(superMethods);
+
+ return new PyFunctionFindUsagesHandler(element, allMethods);
+ }
+ }
+ return new PyFunctionFindUsagesHandler(element);
+ }
+ if (element instanceof PyClass) {
+ return new PyClassFindUsagesHandler((PyClass)element);
+ }
+ return null;
+ }
+
+ /**
+ * @see {@link com.jetbrains.python.findUsages.PyFindUsagesHandlerFactory#isInObject(com.jetbrains.python.psi.PyFunction) isInObject}
+ */
+ private static boolean isInObject(PyFunction fun) {
+ final PyClass containingClass = fun.getContainingClass();
+ if (containingClass == null) {
+ return false;
+ }
+ return (PyNames.FAKE_OLD_BASE.equals(containingClass.getName()) ||
+ (PyNames.OBJECT.equals(containingClass.getName()) && PyBuiltinCache.getInstance(fun).isBuiltin(containingClass)));
+ }
+}
diff --git a/python/src/com/jetbrains/python/hierarchy/treestructures/PySubTypesHierarchyTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/treestructures/PySubTypesHierarchyTreeStructure.java
index 7b43fe7329cf..f4287b28bd6c 100644
--- a/python/src/com/jetbrains/python/hierarchy/treestructures/PySubTypesHierarchyTreeStructure.java
+++ b/python/src/com/jetbrains/python/hierarchy/treestructures/PySubTypesHierarchyTreeStructure.java
@@ -18,9 +18,10 @@ package com.jetbrains.python.hierarchy.treestructures;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.HierarchyTreeStructure;
import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Query;
-import com.jetbrains.python.hierarchy.PyTypeHierarchyNodeDescriptor;
+import com.jetbrains.python.hierarchy.PyHierarchyNodeDescriptor;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.search.PyClassInheritorsSearch;
import org.jetbrains.annotations.NotNull;
@@ -40,17 +41,20 @@ public class PySubTypesHierarchyTreeStructure extends HierarchyTreeStructure {
}
public PySubTypesHierarchyTreeStructure(@NotNull final PyClass cl) {
- super(cl.getProject(), new PyTypeHierarchyNodeDescriptor(null, cl, true));
+ super(cl.getProject(), new PyHierarchyNodeDescriptor(null, cl, true));
}
@NotNull
protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) {
- final PyClass classElement = ((PyTypeHierarchyNodeDescriptor)descriptor).getClassElement();
- Query<PyClass> subClasses = PyClassInheritorsSearch.search(classElement, false);
+ final List<PyHierarchyNodeDescriptor> res = new ArrayList<PyHierarchyNodeDescriptor>();
+ final PsiElement element = ((PyHierarchyNodeDescriptor)descriptor).getPsiElement();
+ if (element instanceof PyClass) {
+ final PyClass cls = (PyClass)element;
+ Query<PyClass> subClasses = PyClassInheritorsSearch.search(cls, false);
+ for (PyClass subClass : subClasses) {
+ res.add(new PyHierarchyNodeDescriptor(descriptor, subClass, false));
+ }
- List<PyTypeHierarchyNodeDescriptor> res = new ArrayList<PyTypeHierarchyNodeDescriptor>();
- for (PyClass cl : subClasses) {
- res.add(new PyTypeHierarchyNodeDescriptor(descriptor, cl, false));
}
return ArrayUtil.toObjectArray(res);
diff --git a/python/src/com/jetbrains/python/hierarchy/treestructures/PySuperTypesHierarchyTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/treestructures/PySuperTypesHierarchyTreeStructure.java
index 38b4c6ee7f76..8087767c61d8 100644
--- a/python/src/com/jetbrains/python/hierarchy/treestructures/PySuperTypesHierarchyTreeStructure.java
+++ b/python/src/com/jetbrains/python/hierarchy/treestructures/PySuperTypesHierarchyTreeStructure.java
@@ -17,8 +17,8 @@ package com.jetbrains.python.hierarchy.treestructures;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.HierarchyTreeStructure;
-import com.intellij.openapi.project.Project;
-import com.jetbrains.python.hierarchy.PyTypeHierarchyNodeDescriptor;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.hierarchy.PyHierarchyNodeDescriptor;
import com.jetbrains.python.psi.PyClass;
import org.jetbrains.annotations.NotNull;
@@ -32,20 +32,23 @@ import java.util.List;
* Time: 7:04:07 PM
*/
public class PySuperTypesHierarchyTreeStructure extends HierarchyTreeStructure {
- protected PySuperTypesHierarchyTreeStructure(final Project project, final HierarchyNodeDescriptor baseDescriptor) {
- super(project, baseDescriptor);
- }
-
public PySuperTypesHierarchyTreeStructure(@NotNull final PyClass cl) {
- super(cl.getProject(), new PyTypeHierarchyNodeDescriptor(null, cl, true));
+ super(cl.getProject(), new PyHierarchyNodeDescriptor(null, cl, true));
}
@NotNull
protected Object[] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) {
- final PyClass[] superClasses = ((PyTypeHierarchyNodeDescriptor)descriptor).getClassElement().getSuperClasses();
- List<PyTypeHierarchyNodeDescriptor> res = new ArrayList<PyTypeHierarchyNodeDescriptor>();
- for (PyClass superClass : superClasses) {
- res.add(new PyTypeHierarchyNodeDescriptor(descriptor, superClass, false));
+ final List<PyHierarchyNodeDescriptor> res = new ArrayList<PyHierarchyNodeDescriptor>();
+ if (descriptor instanceof PyHierarchyNodeDescriptor) {
+ final PyHierarchyNodeDescriptor pyDescriptor = (PyHierarchyNodeDescriptor)descriptor;
+ final PsiElement element = pyDescriptor.getPsiElement();
+ if (element instanceof PyClass) {
+ final PyClass cls = (PyClass)element;
+ final PyClass[] superClasses = cls.getSuperClasses();
+ for (PyClass superClass : superClasses) {
+ res.add(new PyHierarchyNodeDescriptor(descriptor, superClass, false));
+ }
+ }
}
return res.toArray();
}
diff --git a/python/src/com/jetbrains/python/hierarchy/treestructures/PyTypeHierarchyTreeStructure.java b/python/src/com/jetbrains/python/hierarchy/treestructures/PyTypeHierarchyTreeStructure.java
index 6707ac9b7e14..0eb505f4857b 100644
--- a/python/src/com/jetbrains/python/hierarchy/treestructures/PyTypeHierarchyTreeStructure.java
+++ b/python/src/com/jetbrains/python/hierarchy/treestructures/PyTypeHierarchyTreeStructure.java
@@ -16,8 +16,7 @@
package com.jetbrains.python.hierarchy.treestructures;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
-import com.intellij.openapi.project.Project;
-import com.jetbrains.python.hierarchy.PyTypeHierarchyNodeDescriptor;
+import com.jetbrains.python.hierarchy.PyHierarchyNodeDescriptor;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyUtil;
import org.jetbrains.annotations.NotNull;
@@ -28,30 +27,26 @@ import java.util.List;
* @author Alexey.Ivanov
*/
public class PyTypeHierarchyTreeStructure extends PySubTypesHierarchyTreeStructure {
- private static PyTypeHierarchyNodeDescriptor buildHierarchyElement(@NotNull final PyClass cl) {
- PyTypeHierarchyNodeDescriptor descriptor = null;
+ public PyTypeHierarchyTreeStructure(@NotNull final PyClass cl) {
+ super(cl.getProject(), buildHierarchyElement(cl));
+ setBaseElement(myBaseDescriptor);
+ }
+
+ private static PyHierarchyNodeDescriptor buildHierarchyElement(@NotNull final PyClass cl) {
+ PyHierarchyNodeDescriptor descriptor = null;
List<PyClass> superClasses = PyUtil.getAllSuperClasses(cl);
for (int i = superClasses.size() - 1; i >= 0; --i) {
final PyClass superClass = superClasses.get(i);
- final PyTypeHierarchyNodeDescriptor newDescriptor = new PyTypeHierarchyNodeDescriptor(descriptor, superClass, false);
+ final PyHierarchyNodeDescriptor newDescriptor = new PyHierarchyNodeDescriptor(descriptor, superClass, false);
if (descriptor != null) {
- descriptor.setCachedChildren(new PyTypeHierarchyNodeDescriptor[]{newDescriptor});
+ descriptor.setCachedChildren(new PyHierarchyNodeDescriptor[]{newDescriptor});
}
descriptor = newDescriptor;
}
- final PyTypeHierarchyNodeDescriptor newDescriptor = new PyTypeHierarchyNodeDescriptor(descriptor, cl, true);
+ final PyHierarchyNodeDescriptor newDescriptor = new PyHierarchyNodeDescriptor(descriptor, cl, true);
if (descriptor != null) {
descriptor.setCachedChildren(new HierarchyNodeDescriptor[]{newDescriptor});
}
return newDescriptor;
}
-
- protected PyTypeHierarchyTreeStructure(final Project project, final HierarchyNodeDescriptor baseDescriptor) {
- super(project, baseDescriptor);
- }
-
- public PyTypeHierarchyTreeStructure(@NotNull final PyClass cl) {
- super(cl.getProject(), buildHierarchyElement(cl));
- setBaseElement(myBaseDescriptor);
- }
}
diff --git a/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java b/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java
index ec3761154f28..0b73adc3fa4c 100644
--- a/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyPackageRequirementsInspection.java
@@ -27,6 +27,7 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.JDOMExternalizableStringList;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
@@ -42,6 +43,7 @@ import com.jetbrains.python.packaging.*;
import com.jetbrains.python.packaging.ui.PyChooseRequirementsDialog;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
+import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -111,9 +113,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
unsatisfiedNames.add(req.getName());
}
final List<LocalQuickFix> quickFixes = new ArrayList<LocalQuickFix>();
- if (PyPackageManager.getInstance(sdk).hasPip()) {
- quickFixes.add(new PyInstallRequirementsFix(null, module, sdk, unsatisfied));
- }
+ quickFixes.add(new PyInstallRequirementsFix(null, module, sdk, unsatisfied));
quickFixes.add(new IgnoreRequirementFix(unsatisfiedNames));
registerProblem(node, msg,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, null,
@@ -160,15 +160,16 @@ public class PyPackageRequirementsInspection extends PyInspection {
return;
}
}
- if (PyPackageManagerImpl.PACKAGE_SETUPTOOLS.equals(packageName)) {
+ if (PyPackageManager.PACKAGE_SETUPTOOLS.equals(packageName)) {
return;
}
final Module module = ModuleUtilCore.findModuleForPsiElement(packageReferenceExpression);
if (module != null) {
- Collection<PyRequirement> requirements = PyPackageManagerImpl.getRequirements(module);
- if (requirements != null) {
- final Sdk sdk = PythonSdkType.findPythonSdk(module);
- if (sdk != null) {
+ final Sdk sdk = PythonSdkType.findPythonSdk(module);
+ if (sdk != null) {
+ final PyPackageManager manager = PyPackageManager.getInstance(sdk);
+ Collection<PyRequirement> requirements = manager.getRequirements(module);
+ if (requirements != null) {
requirements = getTransitiveRequirements(sdk, requirements, new HashSet<PyPackage>());
}
if (requirements == null) return;
@@ -191,9 +192,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
}
}
final List<LocalQuickFix> quickFixes = new ArrayList<LocalQuickFix>();
- if (sdk != null && PyPackageManager.getInstance(sdk).hasPip()) {
- quickFixes.add(new AddToRequirementsFix(module, packageName, LanguageLevel.forElement(importedExpression)));
- }
+ quickFixes.add(new AddToRequirementsFix(module, packageName, LanguageLevel.forElement(importedExpression)));
quickFixes.add(new IgnoreRequirementFix(Collections.singleton(packageName)));
registerProblem(packageReferenceExpression, String.format("Package '%s' is not listed in project requirements", packageName),
ProblemHighlightType.WEAK_WARNING, null,
@@ -211,7 +210,7 @@ public class PyPackageRequirementsInspection extends PyInspection {
final Set<PyRequirement> results = new HashSet<PyRequirement>(requirements);
final List<PyPackage> packages;
try {
- packages = ((PyPackageManagerImpl) PyPackageManager.getInstance(sdk)).getPackagesFast();
+ packages = PyPackageManager.getInstance(sdk).getPackages(PySdkUtil.isRemote(sdk));
}
catch (PyExternalProcessException e) {
return null;
@@ -242,12 +241,12 @@ public class PyPackageRequirementsInspection extends PyInspection {
@Nullable
private static List<PyRequirement> findUnsatisfiedRequirements(@NotNull Module module, @NotNull Sdk sdk,
@NotNull Set<String> ignoredPackages) {
- final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk);
- List<PyRequirement> requirements = PyPackageManagerImpl.getRequirements(module);
+ final PyPackageManager manager = PyPackageManager.getInstance(sdk);
+ List<PyRequirement> requirements = manager.getRequirements(module);
if (requirements != null) {
final List<PyPackage> packages;
try {
- packages = manager.getPackagesFast();
+ packages = manager.getPackages(PySdkUtil.isRemote(sdk));
}
catch (PyExternalProcessException e) {
return null;
@@ -265,11 +264,11 @@ public class PyPackageRequirementsInspection extends PyInspection {
}
private static void setRunningPackagingTasks(@NotNull Module module, boolean value) {
- module.putUserData(PyPackageManagerImpl.RUNNING_PACKAGING_TASKS, value);
+ module.putUserData(PyPackageManager.RUNNING_PACKAGING_TASKS, value);
}
private static boolean isRunningPackagingTasks(@NotNull Module module) {
- final Boolean value = module.getUserData(PyPackageManagerImpl.RUNNING_PACKAGING_TASKS);
+ final Boolean value = module.getUserData(PyPackageManager.RUNNING_PACKAGING_TASKS);
return value != null && value;
}
@@ -302,6 +301,21 @@ public class PyPackageRequirementsInspection extends PyInspection {
@Override
public void applyFix(@NotNull final Project project, @NotNull ProblemDescriptor descriptor) {
+ boolean installManagement = false;
+ final PyPackageManager manager = PyPackageManager.getInstance(mySdk);
+ if (!manager.hasManagement(false)) {
+ final int result = Messages.showYesNoDialog(project,
+ "Python packaging tools are required for installing packages. Do you want to " +
+ "install 'pip' and 'setuptools' for your interpreter?",
+ "Install Python Packaging Tools",
+ Messages.getQuestionIcon());
+ if (result == Messages.YES) {
+ installManagement = true;
+ }
+ else {
+ return;
+ }
+ }
final List<PyRequirement> chosen;
if (myUnsatisfied.size() > 1) {
final PyChooseRequirementsDialog dialog = new PyChooseRequirementsDialog(project, myUnsatisfied);
@@ -313,21 +327,48 @@ public class PyPackageRequirementsInspection extends PyInspection {
if (chosen.isEmpty()) {
return;
}
- final PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(project, mySdk, new PyPackageManagerImpl.UI.Listener() {
- @Override
- public void started() {
- setRunningPackagingTasks(myModule, true);
- }
+ if (installManagement) {
+ final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule) {
+ @Override
+ public void finished(List<PyExternalProcessException> exceptions) {
+ super.finished(exceptions);
+ if (exceptions.isEmpty()) {
+ installRequirements(project, chosen);
+ }
+ }
+ });
+ ui.installManagement();
+ }
+ else {
+ installRequirements(project, chosen);
+ }
+ }
- @Override
- public void finished(List<PyExternalProcessException> exceptions) {
- setRunningPackagingTasks(myModule, false);
- }
- });
- ui.install(chosen, Collections.<String>emptyList());
+ private void installRequirements(Project project, List<PyRequirement> requirements) {
+ final PyPackageManagerUI ui = new PyPackageManagerUI(project, mySdk, new UIListener(myModule));
+ ui.install(requirements, Collections.<String>emptyList());
+ }
+ }
+
+ private static class UIListener implements PyPackageManagerUI.Listener {
+ private final Module myModule;
+
+ public UIListener(Module module) {
+ myModule = module;
+ }
+
+ @Override
+ public void started() {
+ setRunningPackagingTasks(myModule, true);
+ }
+
+ @Override
+ public void finished(List<PyExternalProcessException> exceptions) {
+ setRunningPackagingTasks(myModule, false);
}
}
+
private static class IgnoreRequirementFix implements LocalQuickFix {
@NotNull private final Set<String> myPackageNames;
diff --git a/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java b/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java
index 7d087bd3eb08..17600d32befb 100644
--- a/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java
+++ b/python/src/com/jetbrains/python/inspections/quickfix/GenerateBinaryStubsFix.java
@@ -170,7 +170,7 @@ public class GenerateBinaryStubsFix implements LocalQuickFix {
new String[]{
homePath,
PythonHelpersLocator.getHelperPath("extra_syspath.py"), myQualifiedName},
- PythonSdkType.getVirtualEnvAdditionalEnv(homePath), 5000
+ PythonSdkType.getVirtualEnvExtraEnv(homePath), 5000
);
if (runResult.getExitCode() == 0 && !runResult.isTimeout()) {
final String extraPath = runResult.getStdout();
diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyDefaultArgumentQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyDefaultArgumentQuickFix.java
index db47f17f8ea8..6e0d5c465d0e 100644
--- a/python/src/com/jetbrains/python/inspections/quickfix/PyDefaultArgumentQuickFix.java
+++ b/python/src/com/jetbrains/python/inspections/quickfix/PyDefaultArgumentQuickFix.java
@@ -61,7 +61,7 @@ public class PyDefaultArgumentQuickFix implements LocalQuickFix {
PyStatementList list = function.getStatementList();
PyParameterList paramList = function.getParameterList();
- final StringBuilder functionText = new StringBuilder("def foo(");
+ final StringBuilder functionText = new StringBuilder("def " + function.getName() + "(");
int size = paramList.getParameters().length;
for (int i = 0; i != size; ++i) {
PyParameter p = paramList.getParameters()[i];
diff --git a/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java
index 43f4f0394015..017bf1a1af29 100644
--- a/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java
+++ b/python/src/com/jetbrains/python/inspections/quickfix/StatementEffectFunctionCallQuickFix.java
@@ -88,7 +88,13 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix {
if (next instanceof PyExpressionStatement) {
final PyExpression expr = ((PyExpressionStatement)next).getExpression();
if (expr instanceof PyBinaryExpression) {
- addInArguments(stringBuilder, (PyBinaryExpression)expr);
+ final PsiElement operator = ((PyBinaryExpression)expr).getPsiOperator();
+ if (operator instanceof LeafPsiElement && ((LeafPsiElement)operator).getElementType() == PyTokenTypes.IN_KEYWORD) {
+ addInArguments(stringBuilder, (PyBinaryExpression)expr);
+ }
+ else {
+ stringBuilder.append(next.getText());
+ }
}
else if (expr instanceof PyTupleExpression) {
final PyExpression[] elements = ((PyTupleExpression)expr).getElements();
@@ -114,14 +120,11 @@ public class StatementEffectFunctionCallQuickFix implements LocalQuickFix {
}
private static void addInArguments(@NotNull final StringBuilder stringBuilder, @NotNull final PyBinaryExpression binaryExpression) {
- final PsiElement operator = binaryExpression.getPsiOperator();
- if (operator instanceof LeafPsiElement && ((LeafPsiElement)operator).getElementType() == PyTokenTypes.IN_KEYWORD) {
- stringBuilder.append(binaryExpression.getLeftExpression().getText());
- stringBuilder.append(", ");
- final PyExpression rightExpression = binaryExpression.getRightExpression();
- if (rightExpression != null)
- stringBuilder.append(rightExpression.getText());
- }
+ stringBuilder.append(binaryExpression.getLeftExpression().getText());
+ stringBuilder.append(", ");
+ final PyExpression rightExpression = binaryExpression.getRightExpression();
+ if (rightExpression != null)
+ stringBuilder.append(rightExpression.getText());
}
private static void replacePrint(@NotNull final PsiElement expression) {
diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java
index b7523caf90e7..8049546a6e92 100644
--- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java
+++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java
@@ -48,7 +48,6 @@ import com.jetbrains.python.documentation.DocStringTypeReference;
import com.jetbrains.python.inspections.*;
import com.jetbrains.python.inspections.quickfix.*;
import com.jetbrains.python.packaging.PyPIPackageUtil;
-import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.packaging.PyRequirement;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
@@ -598,9 +597,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
if (PyPIPackageUtil.INSTANCE.isInPyPI(packageName)) {
final List<PyRequirement> requirements = Collections.singletonList(new PyRequirement(packageName));
final String name = "Install package " + packageName;
- if (PyPackageManager.getInstance(sdk).hasPip()) {
- actions.add(new PyPackageRequirementsInspection.PyInstallRequirementsFix(name, module, sdk, requirements));
- }
+ actions.add(new PyPackageRequirementsInspection.PyInstallRequirementsFix(name, module, sdk, requirements));
}
}
}
diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java
index 65d95eb62713..9b5f669168f7 100644
--- a/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java
+++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java
@@ -15,31 +15,17 @@
*/
package com.jetbrains.python.packaging;
-import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.execution.util.ExecUtil;
-import com.intellij.icons.AllIcons;
-import com.intellij.notification.Notification;
-import com.intellij.notification.NotificationListener;
-import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
-import com.intellij.openapi.progress.ProgressIndicator;
-import com.intellij.openapi.progress.ProgressManager;
-import com.intellij.openapi.progress.Task;
-import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.openapi.projectRoots.SdkAdditionalData;
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
import com.intellij.openapi.roots.OrderRootType;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.util.Key;
-import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
@@ -49,46 +35,36 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
-import com.intellij.remote.RemoteFile;
-import com.intellij.remote.RemoteSdkAdditionalData;
-import com.intellij.remote.RemoteSdkCredentials;
-import com.intellij.remote.VagrantNotStartedException;
import com.intellij.util.ArrayUtil;
-import com.intellij.util.Function;
-import com.intellij.util.PathMappingSettings;
-import com.intellij.util.SystemProperties;
import com.intellij.util.containers.HashSet;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.net.HttpConfigurable;
-import com.intellij.webcore.packaging.PackagesNotificationPanel;
import com.jetbrains.python.PythonHelpersLocator;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyListLiteralExpression;
import com.jetbrains.python.psi.PyStringLiteralExpression;
-import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
-import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import javax.swing.event.HyperlinkEvent;
-import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
-import java.util.List;
/**
* @author vlan
*/
-@SuppressWarnings({"UnusedDeclaration", "FieldAccessedSynchronizedAndUnsynchronized"})
public class PyPackageManagerImpl extends PyPackageManager {
- private static final Logger LOG = Logger.getInstance(PyPackageManagerImpl.class);
+ // Bundled versions of package management tools
+ public static final String SETUPTOOLS_VERSION = "1.1.5";
+ public static final String PIP_VERSION = "1.4.1";
+
+ public static final String SETUPTOOLS = PACKAGE_SETUPTOOLS + "-" + SETUPTOOLS_VERSION;
+ public static final String PIP = PACKAGE_PIP + "-" + PIP_VERSION;
public static final int OK = 0;
- public static final int ERROR_WRONG_USAGE = 1;
public static final int ERROR_NO_PIP = 2;
public static final int ERROR_NO_SETUPTOOLS = 3;
public static final int ERROR_INVALID_SDK = -1;
@@ -97,260 +73,24 @@ public class PyPackageManagerImpl extends PyPackageManager {
public static final int ERROR_INVALID_OUTPUT = -4;
public static final int ERROR_ACCESS_DENIED = -5;
public static final int ERROR_EXECUTION = -6;
- public static final int ERROR_INTERRUPTED = -7;
- public static final int ERROR_VAGRANT_NOT_LAUNCHED = 101;
- public static final int ERROR_REMOTE_ACCESS = 102;
-
- public static final String PACKAGE_PIP = "pip";
- public static final String PACKAGE_DISTRIBUTE = "distribute";
- public static final String PACKAGE_SETUPTOOLS = "setuptools";
-
- public static final Key<Boolean> RUNNING_PACKAGING_TASKS = Key.create("PyPackageRequirementsInspection.RunningPackagingTasks");
+ private static final Logger LOG = Logger.getInstance(PyPackageManagerImpl.class);
private static final String PACKAGING_TOOL = "packaging_tool.py";
private static final String VIRTUALENV = "virtualenv.py";
private static final int TIMEOUT = 10 * 60 * 1000;
private static final String BUILD_DIR_OPTION = "--build-dir";
- public static final String USE_USER_SITE = "--user";
public static final String INSTALL = "install";
public static final String UNINSTALL = "uninstall";
public static final String UNTAR = "untar";
- // Bundled versions of package management tools
- public static final String SETUPTOOLS_VERSION = "1.1.5";
- public static final String PIP_VERSION = "1.4.1";
-
- public static final String SETUPTOOLS = PACKAGE_SETUPTOOLS + "-" + SETUPTOOLS_VERSION;
- public static final String PIP = PACKAGE_PIP + "-" + PIP_VERSION;
- private static final String LAUNCH_VAGRANT = "launchVagrant";
-
private List<PyPackage> myPackagesCache = null;
private Map<String, Set<PyPackage>> myDependenciesCache = null;
private PyExternalProcessException myExceptionCache = null;
- private Sdk mySdk;
-
- public static class UI {
- @Nullable private Listener myListener;
- @NotNull private Project myProject;
- @NotNull private Sdk mySdk;
-
- public interface Listener {
- void started();
-
- void finished(List<PyExternalProcessException> exceptions);
- }
-
- public UI(@NotNull Project project, @NotNull Sdk sdk, @Nullable Listener listener) {
- myProject = project;
- mySdk = sdk;
- myListener = listener;
- }
-
- public void installManagement(@NotNull final String name) {
- final String progressTitle;
- final String successTitle;
- progressTitle = "Installing package " + name;
- successTitle = "Packages installed successfully";
- run(new MultiExternalRunnable() {
- @Override
- public List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator) {
- final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>();
- indicator.setText(String.format("Installing package '%s'...", name));
- final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManagers.getInstance().forSdk(mySdk);
- try {
- manager.installManagement(name);
- }
- catch (PyExternalProcessException e) {
- exceptions.add(e);
- }
- return exceptions;
- }
- }, progressTitle, successTitle, "Installed package " + name,
- "Install package failed"
- );
- }
-
- public void install(@NotNull final List<PyRequirement> requirements, @NotNull final List<String> extraArgs) {
- final String progressTitle;
- final String successTitle;
- progressTitle = "Installing packages";
- successTitle = "Packages installed successfully";
- run(new MultiExternalRunnable() {
- @Override
- public List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator) {
- final int size = requirements.size();
- final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>();
- final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManagers.getInstance().forSdk(mySdk);
- for (int i = 0; i < size; i++) {
- final PyRequirement requirement = requirements.get(i);
- if (myListener != null) {
- indicator.setText(String.format("Installing package '%s'...", requirement));
- indicator.setFraction((double)i / size);
- }
- try {
- manager.install(list(requirement), extraArgs);
- }
- catch (PyExternalProcessException e) {
- exceptions.add(e);
- }
- }
- manager.refresh();
- return exceptions;
- }
- }, progressTitle, successTitle, "Installed packages: " + PyPackageUtil.requirementsToString(requirements),
- "Install packages failed"
- );
- }
-
- public void uninstall(@NotNull final List<PyPackage> packages) {
- final String packagesString = StringUtil.join(packages, new Function<PyPackage, String>() {
- @Override
- public String fun(PyPackage pkg) {
- return "'" + pkg.getName() + "'";
- }
- }, ", ");
- if (checkDependents(packages)) return;
-
- run(new MultiExternalRunnable() {
- @Override
- public List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator) {
- final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManagers.getInstance().forSdk(mySdk);
- try {
- manager.uninstall(packages);
- return list();
- }
- catch (PyExternalProcessException e) {
- return list(e);
- }
- finally {
- manager.refresh();
- }
- }
- }, "Uninstalling packages", "Packages uninstalled successfully", "Uninstalled packages: " + packagesString,
- "Uninstall packages failed"
- );
- }
-
- private boolean checkDependents(@NotNull final List<PyPackage> packages) {
- try {
- final Map<String, Set<PyPackage>> dependentPackages = collectDependents(packages, mySdk);
- final int[] warning = {0};
- if (!dependentPackages.isEmpty()) {
- ApplicationManager.getApplication().invokeAndWait(new Runnable() {
- @Override
- public void run() {
- if (dependentPackages.size() == 1) {
- String message = "You are attempting to uninstall ";
- List<String> dep = new ArrayList<String>();
- int size = 1;
- for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) {
- final Set<PyPackage> value = entry.getValue();
- size = value.size();
- dep.add(entry.getKey() + " package which is required for " + StringUtil.join(value, ", "));
- }
- message += StringUtil.join(dep, "\n");
- message += size == 1 ? " package" : " packages";
- message += "\n\nDo you want to proceed?";
- warning[0] = Messages.showYesNoDialog(message, "Warning",
- AllIcons.General.BalloonWarning);
- }
- else {
- String message = "You are attempting to uninstall packages which are required for another packages.\n\n";
- List<String> dep = new ArrayList<String>();
- for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) {
- dep.add(entry.getKey() + " -> " + StringUtil.join(entry.getValue(), ", "));
- }
- message += StringUtil.join(dep, "\n");
- message += "\n\nDo you want to proceed?";
- warning[0] = Messages.showYesNoDialog(message, "Warning",
- AllIcons.General.BalloonWarning);
- }
- }
- }, ModalityState.current());
- }
- if (warning[0] != Messages.YES) return true;
- }
- catch (PyExternalProcessException e) {
- LOG.info("Error loading packages dependents: " + e.getMessage(), e);
- }
- return false;
- }
-
- private interface MultiExternalRunnable {
- List<PyExternalProcessException> run(@NotNull ProgressIndicator indicator);
- }
-
- private void run(@NotNull final MultiExternalRunnable runnable, @NotNull final String progressTitle,
- @NotNull final String successTitle, @NotNull final String successDescription, @NotNull final String failureTitle) {
- ProgressManager.getInstance().run(new Task.Backgroundable(myProject, progressTitle, false) {
- @Override
- public void run(@NotNull ProgressIndicator indicator) {
- indicator.setText(progressTitle + "...");
- final Ref<Notification> notificationRef = new Ref<Notification>(null);
- final String PACKAGING_GROUP_ID = "Packaging";
- final Application application = ApplicationManager.getApplication();
- if (myListener != null) {
- application.invokeLater(new Runnable() {
- @Override
- public void run() {
- myListener.started();
- }
- });
- }
-
- final List<PyExternalProcessException> exceptions = runnable.run(indicator);
- if (exceptions.isEmpty()) {
- notificationRef.set(new Notification(PACKAGING_GROUP_ID, successTitle, successDescription, NotificationType.INFORMATION));
- }
- else {
- final String progressLower = progressTitle.toLowerCase();
- final String firstLine = String.format("Error%s occurred when %s.", exceptions.size() > 1 ? "s" : "", progressLower);
-
- final String description = createDescription(exceptions, firstLine);
- notificationRef.set(new Notification(PACKAGING_GROUP_ID, failureTitle,
- firstLine + " <a href=\"xxx\">Details...</a>",
- NotificationType.ERROR,
- new NotificationListener() {
- @Override
- public void hyperlinkUpdate(@NotNull Notification notification,
- @NotNull HyperlinkEvent event) {
- assert myProject != null;
- PackagesNotificationPanel.showError(myProject, failureTitle, description);
- }
- }
- ));
- }
- application.invokeLater(new Runnable() {
- @Override
- public void run() {
- if (myListener != null) {
- myListener.finished(exceptions);
- }
- final Notification notification = notificationRef.get();
- if (notification != null) {
- notification.notify(myProject);
- }
- }
- });
- }
- });
- }
-
- public static String createDescription(List<PyExternalProcessException> exceptions, String firstLine) {
- final StringBuilder b = new StringBuilder();
- b.append(firstLine);
- b.append("\n\n");
- for (PyExternalProcessException exception : exceptions) {
- b.append(exception.toString());
- b.append("\n");
- }
- return b.toString();
- }
- }
+ protected Sdk mySdk;
@Override
public void refresh() {
@@ -373,7 +113,23 @@ public class PyPackageManagerImpl extends PyPackageManager {
});
}
- private void installManagement(String name) throws PyExternalProcessException {
+ @Override
+ public void installManagement() throws PyExternalProcessException {
+ if (!hasPackage(PACKAGE_SETUPTOOLS, false) && !hasPackage(PACKAGE_DISTRIBUTE, false)) {
+ installManagement(SETUPTOOLS);
+ }
+ if (!hasPackage(PACKAGE_PIP, false)) {
+ installManagement(PIP);
+ }
+ }
+
+ @Override
+ public boolean hasManagement(boolean cachedOnly) {
+ return (hasPackage(PACKAGE_SETUPTOOLS, cachedOnly) || hasPackage(PACKAGE_DISTRIBUTE, cachedOnly)) &&
+ hasPackage(PACKAGE_PIP, cachedOnly);
+ }
+
+ protected void installManagement(@NotNull String name) throws PyExternalProcessException {
final String helperPath = getHelperPath(name);
ArrayList<String> args = Lists.newArrayList(UNTAR, helperPath);
@@ -390,7 +146,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
final String fileName = dirName + name + File.separatorChar + "setup.py";
try {
- output = getProcessOutput(fileName, Collections.<String>singletonList(INSTALL), true, dirName + name);
+ output = getProcessOutput(fileName, Collections.singletonList(INSTALL), true, dirName + name);
final int retcode = output.getExitCode();
if (output.isTimeout()) {
throw new PyExternalProcessException(ERROR_TIMEOUT, fileName, Lists.newArrayList(INSTALL), "Timed out");
@@ -406,17 +162,28 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
finally {
clearCaches();
- FileUtil.delete(new File(dirName)); //TODO: remove temp directory for remote interpreter
+ FileUtil.delete(new File(dirName));
+ }
+ }
+
+ private boolean hasPackage(@NotNull String name, boolean cachedOnly) {
+ try {
+ return findPackage(name, cachedOnly) != null;
+ }
+ catch (PyExternalProcessException ignored) {
+ return false;
}
}
PyPackageManagerImpl(@NotNull Sdk sdk) {
mySdk = sdk;
+ subscribeToLocalChanges(sdk);
+ }
+
+ protected void subscribeToLocalChanges(Sdk sdk) {
final Application app = ApplicationManager.getApplication();
final MessageBusConnection connection = app.getMessageBus().connect();
- if (!PySdkUtil.isRemote(sdk)) {
- connection.subscribe(VirtualFileManager.VFS_CHANGES, new MySdkRootWatcher());
- }
+ connection.subscribe(VirtualFileManager.VFS_CHANGES, new MySdkRootWatcher());
}
public Sdk getSdk() {
@@ -424,27 +191,13 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
@Override
- public void install(String requirementString) throws PyExternalProcessException {
- boolean hasSetuptools = false;
- boolean hasPip = false;
- try {
- hasSetuptools = findInstalledPackage(SETUPTOOLS) != null;
- }
- catch (PyExternalProcessException ignored) {
- }
- try {
- hasPip = findInstalledPackage(PIP) != null;
- }
- catch (PyExternalProcessException ignored) {
- }
-
- if (!hasSetuptools) installManagement(SETUPTOOLS);
- if (!hasPip) installManagement(PIP);
+ public void install(@NotNull String requirementString) throws PyExternalProcessException {
+ installManagement();
install(Collections.singletonList(PyRequirement.fromString(requirementString)), Collections.<String>emptyList());
}
- public void install(@NotNull List<PyRequirement> requirements, @NotNull List<String> extraArgs)
- throws PyExternalProcessException {
+ @Override
+ public void install(@NotNull List<PyRequirement> requirements, @NotNull List<String> extraArgs) throws PyExternalProcessException {
final List<String> args = new ArrayList<String>();
args.add(INSTALL);
final File buildDir;
@@ -455,7 +208,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
throw new PyExternalProcessException(ERROR_ACCESS_DENIED, PACKAGING_TOOL, args, "Cannot create temporary build directory");
}
if (!extraArgs.contains(BUILD_DIR_OPTION)) {
- args.addAll(list(BUILD_DIR_OPTION, buildDir.getAbsolutePath()));
+ args.addAll(Arrays.asList(BUILD_DIR_OPTION, buildDir.getAbsolutePath()));
}
boolean useUserSite = extraArgs.contains(USE_USER_SITE);
@@ -499,69 +252,22 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
}
- private static Map<String, Set<PyPackage>> collectDependents(@NotNull final List<PyPackage> packages, Sdk sdk)
- throws PyExternalProcessException {
- Map<String, Set<PyPackage>> dependentPackages = new HashMap<String, Set<PyPackage>>();
- for (PyPackage pkg : packages) {
- final Set<PyPackage> dependents =
- ((PyPackageManagerImpl)PyPackageManager.getInstance(sdk)).getDependents(pkg.getName());
- if (dependents != null && !dependents.isEmpty()) {
- for (PyPackage dependent : dependents) {
- if (!packages.contains(dependent)) {
- dependentPackages.put(pkg.getName(), dependents);
- }
- }
- }
- }
- return dependentPackages;
- }
-
- public static String getUserSite() {
- if (SystemInfo.isWindows) {
- final String appdata = System.getenv("APPDATA");
- return appdata + File.separator + "Python";
- }
- else {
- final String userHome = SystemProperties.getUserHome();
- return userHome + File.separator + ".local";
- }
- }
-
-
- public boolean cacheIsNotNull() {
- return myPackagesCache != null;
- }
-
- /**
- * Returns the list of packages for the SDK without initiating a remote connection. Returns null
- * for a remote interpreter if the list of packages was not loaded.
- *
- * @return the list of packages or null
- */
@Nullable
- public synchronized List<PyPackage> getPackagesFast() throws PyExternalProcessException {
- if (myPackagesCache != null) {
- return myPackagesCache;
- }
- if (PySdkUtil.isRemote(mySdk)) {
- return null;
- }
- return getPackages();
- }
-
- @NotNull
- public synchronized List<PyPackage> getPackages() throws PyExternalProcessException {
+ public synchronized List<PyPackage> getPackages(boolean cachedOnly) throws PyExternalProcessException {
if (myPackagesCache == null) {
if (myExceptionCache != null) {
throw myExceptionCache;
}
-
+ if (cachedOnly) {
+ return null;
+ }
loadPackages();
}
return myPackagesCache;
}
- public synchronized Set<PyPackage> getDependents(String pkg) throws PyExternalProcessException {
+ @Nullable
+ public synchronized Set<PyPackage> getDependents(@NotNull PyPackage pkg) throws PyExternalProcessException {
if (myDependenciesCache == null) {
if (myExceptionCache != null) {
throw myExceptionCache;
@@ -569,12 +275,12 @@ public class PyPackageManagerImpl extends PyPackageManager {
loadPackages();
}
- return myDependenciesCache.get(pkg);
+ return myDependenciesCache.get(pkg.getName());
}
public synchronized void loadPackages() throws PyExternalProcessException {
try {
- final String output = runPythonHelper(PACKAGING_TOOL, list("list"));
+ final String output = runPythonHelper(PACKAGING_TOOL, Arrays.asList("list"));
myPackagesCache = parsePackagingToolOutput(output);
Collections.sort(myPackagesCache, new Comparator<PyPackage>() {
@Override
@@ -592,7 +298,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
}
- private void calculateDependents() {
+ private synchronized void calculateDependents() {
myDependenciesCache = new HashMap<String, Set<PyPackage>>();
for (PyPackage p : myPackagesCache) {
final List<PyRequirement> requirements = p.getRequirements();
@@ -608,47 +314,18 @@ public class PyPackageManagerImpl extends PyPackageManager {
@Override
@Nullable
- public PyPackage findInstalledPackage(String name) throws PyExternalProcessException {
- return findPackageByName(name, getPackages());
- }
-
- @Override
- public boolean findPackage(@NotNull final String name) {
- try {
- final String output = runPythonHelper(PACKAGING_TOOL, list("search", name));
- return StringUtil.containsIgnoreCase(output, name + " ");
- }
- catch (PyExternalProcessException e) {
- LOG.error(e.getMessage());
- return false;
- }
- }
-
- @Nullable
- public PyPackage findPackageFast(String name) throws PyExternalProcessException {
- final List<PyPackage> packages = getPackagesFast();
- return packages != null ? findPackageByName(name, packages) : null;
- }
-
- @Nullable
- private static PyPackage findPackageByName(String name, List<PyPackage> packages) {
- for (PyPackage pkg : packages) {
- if (name.equalsIgnoreCase(pkg.getName())) {
- return pkg;
+ public PyPackage findPackage(@NotNull String name, boolean cachedOnly) throws PyExternalProcessException {
+ final List<PyPackage> packages = getPackages(cachedOnly);
+ if (packages != null) {
+ for (PyPackage pkg : packages) {
+ if (name.equalsIgnoreCase(pkg.getName())) {
+ return pkg;
+ }
}
}
return null;
}
- public boolean hasPip() {
- try {
- return findPackageFast(PACKAGE_PIP) != null;
- }
- catch (PyExternalProcessException e) {
- return false;
- }
- }
-
@NotNull
public String createVirtualEnv(@NotNull String destinationDir, boolean useGlobalSite) throws PyExternalProcessException {
final List<String> args = new ArrayList<String>();
@@ -674,30 +351,21 @@ public class PyPackageManagerImpl extends PyPackageManager {
final String path = (binary != null) ? binary : binaryFallback;
if (usePyVenv) {
- // TODO: Still no 'packaging' and 'pysetup3' for Python 3.3rc1, see PEP 405
+ // Still no 'packaging' and 'pysetup3' for Python 3.3rc1, see PEP 405
final VirtualFile binaryFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
if (binaryFile != null) {
final ProjectJdkImpl tmpSdk = new ProjectJdkImpl("", PythonSdkType.getInstance());
tmpSdk.setHomePath(path);
- final PyPackageManagerImpl manager = new PyPackageManagerImpl(tmpSdk);
- manager.installManagement(SETUPTOOLS);
- manager.installManagement(PIP);
+ final PyPackageManager manager = PyPackageManager.getInstance(tmpSdk);
+ manager.installManagement();
}
}
return path;
}
- public static void deleteVirtualEnv(@NotNull String sdkHome) throws PyExternalProcessException {
- final File root = PythonSdkType.getVirtualEnvRoot(sdkHome);
- if (root != null) {
- FileUtil.delete(root);
- }
- }
-
@Nullable
- public static List<PyRequirement> getRequirements(@NotNull Module module) {
- // TODO: Cache requirements, clear cache on requirements.txt or setup.py updates
- List<PyRequirement> requirements = getRequirementsFromTxt(module);
+ public List<PyRequirement> getRequirements(@NotNull Module module) {
+ List<PyRequirement> requirements = PySdkUtil.getRequirementsFromTxt(module);
if (requirements != null) {
return requirements;
}
@@ -721,25 +389,12 @@ public class PyPackageManagerImpl extends PyPackageManager {
return null;
}
- @Nullable
- public static List<PyRequirement> getRequirementsFromTxt(Module module) {
- final VirtualFile requirementsTxt = PyPackageUtil.findRequirementsTxt(module);
- if (requirementsTxt != null) {
- return PyRequirement.parse(requirementsTxt);
- }
- return null;
- }
-
- private void clearCaches() {
+ protected synchronized void clearCaches() {
myPackagesCache = null;
myDependenciesCache = null;
myExceptionCache = null;
}
- private static <T> List<T> list(T... xs) {
- return Arrays.asList(xs);
- }
-
@Nullable
private static String getProxyString() {
final HttpConfigurable settings = HttpConfigurable.getInstance();
@@ -792,164 +447,61 @@ public class PyPackageManagerImpl extends PyPackageManager {
}
@Nullable
- private String getHelperPath(String helper) {
- String helperPath;
- final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData();
- if (sdkData instanceof PyRemoteSdkAdditionalDataBase) {
- PyRemoteSdkAdditionalDataBase remoteSdkData = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
-
- try {
- final RemoteSdkCredentials remoteSdkCredentials = remoteSdkData.getRemoteSdkCredentials(false);
- if (!StringUtil.isEmpty(remoteSdkCredentials.getHelpersPath())) {
- helperPath = new RemoteFile(remoteSdkCredentials.getHelpersPath(),
- helper).getPath();
- }
- else {
- helperPath = null;
- }
- }
- catch (Exception e) {
- helperPath = null;
- LOG.error(e);
- }
- }
- else {
- helperPath = PythonHelpersLocator.getHelperPath(helper);
- }
- return helperPath;
+ protected String getHelperPath(String helper) {
+ return PythonHelpersLocator.getHelperPath(helper);
}
- private ProcessOutput getProcessOutput(@NotNull String helperPath,
+ protected ProcessOutput getProcessOutput(@NotNull String helperPath,
@NotNull List<String> args,
boolean askForSudo,
- @Nullable String workingDir)
- throws PyExternalProcessException {
- final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData();
+ @Nullable String workingDir) throws PyExternalProcessException {
final String homePath = mySdk.getHomePath();
if (homePath == null) {
throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Cannot find interpreter for SDK");
}
- if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { //remote interpreter
- RemoteSdkCredentials remoteSdkCredentials;
+ if (workingDir == null) {
+ workingDir = new File(homePath).getParent();
+ }
+ final List<String> cmdline = new ArrayList<String>();
+ cmdline.add(homePath);
+ cmdline.add(helperPath);
+ cmdline.addAll(args);
+ LOG.info("Running packaging tool: " + StringUtil.join(cmdline, " "));
+
+ final boolean canCreate = FileUtil.ensureCanCreateFile(new File(homePath));
+ if (!canCreate && !SystemInfo.isWindows && askForSudo) { //is system site interpreter --> we need sudo privileges
try {
- remoteSdkCredentials = ((RemoteSdkAdditionalData)sdkData).getRemoteSdkCredentials(false);
- }
- catch (InterruptedException e) {
- LOG.error(e);
- remoteSdkCredentials = null;
- }
- catch (final ExecutionException e) {
- if (e.getCause() instanceof VagrantNotStartedException) {
- throw new PyExternalProcessException(ERROR_VAGRANT_NOT_LAUNCHED, helperPath, args, "Vagrant instance is down. <a href=\"" +
- LAUNCH_VAGRANT +
- "\">Launch vagrant</a>")
- .withHandler(LAUNCH_VAGRANT, new Runnable() {
- @Override
- public void run() {
- final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
- if (manager != null) {
-
- try {
- manager.runVagrant(((VagrantNotStartedException)e.getCause()).getVagrantFolder());
- clearCaches();
- }
- catch (ExecutionException e1) {
- throw new RuntimeException(e1);
- }
- }
- }
- });
- }
- else {
- throw new PyExternalProcessException(ERROR_REMOTE_ACCESS, helperPath, args, e.getMessage());
- }
- }
- final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
- if (manager != null && remoteSdkCredentials != null) {
- final List<String> cmdline = new ArrayList<String>();
- cmdline.add(homePath);
- cmdline.add(RemoteFile.detectSystemByPath(homePath).createRemoteFile(helperPath).getPath());
- cmdline.addAll(Collections2.transform(args, new com.google.common.base.Function<String, String>() {
- @Override
- public String apply(@Nullable String input) {
- return quoteIfNeeded(input);
+ final ProcessOutput result = ExecUtil.sudoAndGetOutput(cmdline,
+ "Please enter your password to make changes in system packages: ",
+ workingDir);
+ String message = result.getStderr();
+ if (result.getExitCode() != 0) {
+ final String stdout = result.getStdout();
+ if (StringUtil.isEmptyOrSpaces(message)) {
+ message = stdout;
}
- }));
- try {
- if (askForSudo) {
- askForSudo = !manager.ensureCanWrite(null, remoteSdkCredentials, remoteSdkCredentials.getInterpreterPath());
+ if (StringUtil.isEmptyOrSpaces(message)) {
+ message = "Failed to perform action. Permission denied.";
}
- ProcessOutput processOutput;
- do {
- PathMappingSettings mappings = manager.setupMappings(null, (PyRemoteSdkAdditionalDataBase)sdkData, null);
- processOutput =
- manager.runRemoteProcess(null, remoteSdkCredentials, mappings, ArrayUtil.toStringArray(cmdline), workingDir, askForSudo);
- if (askForSudo && processOutput.getStderr().contains("sudo: 3 incorrect password attempts")) {
- continue;
- }
- break;
- }
- while (true);
- return processOutput;
+ throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message);
}
- catch (ExecutionException e) {
- throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Error running SDK: " + e.getMessage(), e);
+ if (SystemInfo.isMac && !StringUtil.isEmptyOrSpaces(message)) {
+ throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message);
}
+ return result;
}
- else {
- throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args,
- PythonRemoteInterpreterManager.WEB_DEPLOYMENT_PLUGIN_IS_DISABLED);
+ catch (ExecutionException e) {
+ throw new PyExternalProcessException(ERROR_EXECUTION, helperPath, args, e.getMessage());
+ }
+ catch (IOException e) {
+ throw new PyExternalProcessException(ERROR_ACCESS_DENIED, helperPath, args, e.getMessage());
}
}
else {
- if (workingDir == null) {
- workingDir = new File(homePath).getParent();
- }
- final List<String> cmdline = new ArrayList<String>();
- cmdline.add(homePath);
- cmdline.add(helperPath);
- cmdline.addAll(args);
- LOG.info("Running packaging tool: " + StringUtil.join(cmdline, " "));
-
- final boolean canCreate = FileUtil.ensureCanCreateFile(new File(homePath));
- if (!canCreate && !SystemInfo.isWindows && askForSudo) { //is system site interpreter --> we need sudo privileges
- try {
- final ProcessOutput result = ExecUtil.sudoAndGetOutput(cmdline,
- "Please enter your password to make changes in system packages: ",
- workingDir);
- String message = result.getStderr();
- if (result.getExitCode() != 0) {
- final String stdout = result.getStdout();
- if (StringUtil.isEmptyOrSpaces(message)) {
- message = stdout;
- }
- if (StringUtil.isEmptyOrSpaces(message)) {
- message = "Failed to perform action. Permission denied.";
- }
- throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message);
- }
- if (SystemInfo.isMac && !StringUtil.isEmptyOrSpaces(message)) {
- throw new PyExternalProcessException(result.getExitCode(), helperPath, args, message);
- }
- return result;
- }
- catch (ExecutionException e) {
- throw new PyExternalProcessException(ERROR_EXECUTION, helperPath, args, e.getMessage());
- }
- catch (IOException e) {
- throw new PyExternalProcessException(ERROR_ACCESS_DENIED, helperPath, args, e.getMessage());
- }
- }
- else {
- return PySdkUtil.getProcessOutput(workingDir, ArrayUtil.toStringArray(cmdline), TIMEOUT);
- }
+ return PySdkUtil.getProcessOutput(workingDir, ArrayUtil.toStringArray(cmdline), TIMEOUT);
}
}
- private static String quoteIfNeeded(String arg) {
- return arg.replace("<", "\\<").replace(">", "\\>"); //TODO: move this logic to ParametersListUtil.encode
- }
-
@NotNull
private static List<PyPackage> parsePackagingToolOutput(@NotNull String s) throws PyExternalProcessException {
final String[] lines = StringUtil.splitByLines(s);
@@ -976,17 +528,6 @@ public class PyPackageManagerImpl extends PyPackageManager {
return packages;
}
-
- @Override
- public void showInstallationError(Project project, String title, String description) {
- PackagesNotificationPanel.showError(project, title, description);
- }
-
- @Override
- public void showInstallationError(Component owner, String title, String description) {
- PackagesNotificationPanel.showError(owner, title, description);
- }
-
private class MySdkRootWatcher extends BulkFileListener.Adapter {
@Override
public void after(@NotNull List<? extends VFileEvent> events) {
diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java
new file mode 100644
index 000000000000..28dfa6c834a4
--- /dev/null
+++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.packaging;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationListener;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.Function;
+import com.intellij.webcore.packaging.PackagesNotificationPanel;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.event.HyperlinkEvent;
+import java.util.*;
+
+/**
+* @author vlan
+*/
+public class PyPackageManagerUI {
+ private static final Logger LOG = Logger.getInstance(PyPackageManagerUI.class);
+ @Nullable private Listener myListener;
+ @NotNull private Project myProject;
+ @NotNull private Sdk mySdk;
+
+ public interface Listener {
+ void started();
+
+ void finished(List<PyExternalProcessException> exceptions);
+ }
+
+ public PyPackageManagerUI(@NotNull Project project, @NotNull Sdk sdk, @Nullable Listener listener) {
+ myProject = project;
+ mySdk = sdk;
+ myListener = listener;
+ }
+
+ public void installManagement() {
+ ProgressManager.getInstance().run(new InstallManagementTask(myProject, mySdk, myListener));
+ }
+
+ public void install(@NotNull final List<PyRequirement> requirements, @NotNull final List<String> extraArgs) {
+ ProgressManager.getInstance().run(new InstallTask(myProject, mySdk, requirements, extraArgs, myListener));
+ }
+
+ public void uninstall(@NotNull final List<PyPackage> packages) {
+ if (checkDependents(packages)) {
+ return;
+ }
+ ProgressManager.getInstance().run(new UninstallTask(myProject, mySdk, myListener, packages));
+ }
+
+ private boolean checkDependents(@NotNull final List<PyPackage> packages) {
+ try {
+ final Map<String, Set<PyPackage>> dependentPackages = collectDependents(packages, mySdk);
+ final int[] warning = {0};
+ if (!dependentPackages.isEmpty()) {
+ ApplicationManager.getApplication().invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ if (dependentPackages.size() == 1) {
+ String message = "You are attempting to uninstall ";
+ List<String> dep = new ArrayList<String>();
+ int size = 1;
+ for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) {
+ final Set<PyPackage> value = entry.getValue();
+ size = value.size();
+ dep.add(entry.getKey() + " package which is required for " + StringUtil.join(value, ", "));
+ }
+ message += StringUtil.join(dep, "\n");
+ message += size == 1 ? " package" : " packages";
+ message += "\n\nDo you want to proceed?";
+ warning[0] = Messages.showYesNoDialog(message, "Warning",
+ AllIcons.General.BalloonWarning);
+ }
+ else {
+ String message = "You are attempting to uninstall packages which are required for another packages.\n\n";
+ List<String> dep = new ArrayList<String>();
+ for (Map.Entry<String, Set<PyPackage>> entry : dependentPackages.entrySet()) {
+ dep.add(entry.getKey() + " -> " + StringUtil.join(entry.getValue(), ", "));
+ }
+ message += StringUtil.join(dep, "\n");
+ message += "\n\nDo you want to proceed?";
+ warning[0] = Messages.showYesNoDialog(message, "Warning",
+ AllIcons.General.BalloonWarning);
+ }
+ }
+ }, ModalityState.current());
+ }
+ if (warning[0] != Messages.YES) return true;
+ }
+ catch (PyExternalProcessException e) {
+ LOG.info("Error loading packages dependents: " + e.getMessage(), e);
+ }
+ return false;
+ }
+
+ private static Map<String, Set<PyPackage>> collectDependents(@NotNull final List<PyPackage> packages, Sdk sdk)
+ throws PyExternalProcessException {
+ Map<String, Set<PyPackage>> dependentPackages = new HashMap<String, Set<PyPackage>>();
+ for (PyPackage pkg : packages) {
+ final Set<PyPackage> dependents = PyPackageManager.getInstance(sdk).getDependents(pkg);
+ if (dependents != null && !dependents.isEmpty()) {
+ for (PyPackage dependent : dependents) {
+ if (!packages.contains(dependent)) {
+ dependentPackages.put(pkg.getName(), dependents);
+ }
+ }
+ }
+ }
+ return dependentPackages;
+ }
+
+ private abstract static class PackagingTask extends Task.Backgroundable {
+ private static final String PACKAGING_GROUP_ID = "Packaging";
+
+ @Nullable protected final Listener myListener;
+
+ public PackagingTask(@Nullable Project project, @NotNull String title, @Nullable Listener listener) {
+ super(project, title);
+ myListener = listener;
+ }
+
+ @Override
+ public void run(@NotNull ProgressIndicator indicator) {
+ taskStarted(indicator);
+ taskFinished(runTask(indicator));
+ }
+
+ @NotNull
+ protected abstract List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator);
+
+ @NotNull
+ protected abstract String getSuccessTitle();
+
+ @NotNull
+ protected abstract String getSuccessDescription();
+
+ @NotNull
+ protected abstract String getFailureTitle();
+
+ protected void taskStarted(@NotNull ProgressIndicator indicator) {
+ indicator.setText(getTitle() + "...");
+ if (myListener != null) {
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ myListener.started();
+ }
+ });
+ }
+ }
+
+ protected void taskFinished(@NotNull final List<PyExternalProcessException> exceptions) {
+ final Ref<Notification> notificationRef = new Ref<Notification>(null);
+ if (exceptions.isEmpty()) {
+ notificationRef.set(new Notification(PACKAGING_GROUP_ID, getSuccessTitle(), getSuccessDescription(),
+ NotificationType.INFORMATION));
+ }
+ else {
+ final String firstLine = getTitle() + ": error occurred.";
+ final String description = createDescription(exceptions, firstLine);
+ notificationRef.set(new Notification(PACKAGING_GROUP_ID, getFailureTitle(),
+ firstLine + " <a href=\"xxx\">Details...</a>",
+ NotificationType.ERROR,
+ new NotificationListener() {
+ @Override
+ public void hyperlinkUpdate(@NotNull Notification notification,
+ @NotNull HyperlinkEvent event) {
+ assert myProject != null;
+ PackagesNotificationPanel.showError(myProject, getFailureTitle(), description);
+ }
+ }
+ ));
+ }
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ if (myListener != null) {
+ myListener.finished(exceptions);
+ }
+ final Notification notification = notificationRef.get();
+ if (notification != null) {
+ notification.notify(myProject);
+ }
+ }
+ });
+ }
+ }
+
+ private static class InstallTask extends PackagingTask {
+ @NotNull protected final Sdk mySdk;
+ @NotNull private final List<PyRequirement> myRequirements;
+ @NotNull private final List<String> myExtraArgs;
+
+ public InstallTask(@Nullable Project project,
+ @NotNull Sdk sdk,
+ @NotNull List<PyRequirement> requirements,
+ @NotNull List<String> extraArgs,
+ @Nullable Listener listener) {
+ super(project, "Installing packages", listener);
+ mySdk = sdk;
+ myRequirements = requirements;
+ myExtraArgs = extraArgs;
+ }
+
+ @NotNull
+ @Override
+ protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) {
+ final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>();
+ final int size = myRequirements.size();
+ final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk);
+ for (int i = 0; i < size; i++) {
+ final PyRequirement requirement = myRequirements.get(i);
+ indicator.setText(String.format("Installing package '%s'...", requirement));
+ indicator.setFraction((double)i / size);
+ try {
+ manager.install(Arrays.asList(requirement), myExtraArgs);
+ }
+ catch (PyExternalProcessException e) {
+ exceptions.add(e);
+ }
+ }
+ manager.refresh();
+ return exceptions;
+ }
+
+ @NotNull
+ @Override
+ protected String getSuccessTitle() {
+ return "Packages installed successfully";
+ }
+
+ @NotNull
+ @Override
+ protected String getSuccessDescription() {
+ return "Installed packages: " + PyPackageUtil.requirementsToString(myRequirements);
+ }
+
+ @NotNull
+ @Override
+ protected String getFailureTitle() {
+ return "Install packages failed";
+ }
+ }
+
+ private static class InstallManagementTask extends InstallTask {
+
+ public InstallManagementTask(@Nullable Project project,
+ @NotNull Sdk sdk,
+ @Nullable Listener listener) {
+ super(project, sdk, Collections.<PyRequirement>emptyList(), Collections.<String>emptyList(), listener);
+ }
+
+ @NotNull
+ @Override
+ protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) {
+ final List<PyExternalProcessException> exceptions = new ArrayList<PyExternalProcessException>();
+ final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk);
+ indicator.setText("Installing packaging tools...");
+ indicator.setIndeterminate(true);
+ try {
+ manager.installManagement();
+ }
+ catch (PyExternalProcessException e) {
+ exceptions.add(e);
+ }
+ manager.refresh();
+ return exceptions;
+ }
+
+ @NotNull
+ @Override
+ protected String getSuccessDescription() {
+ return "Installed Python packaging tools";
+ }
+ }
+
+ private static class UninstallTask extends PackagingTask {
+ @NotNull private final Sdk mySdk;
+ @NotNull private final List<PyPackage> myPackages;
+
+ public UninstallTask(@Nullable Project project,
+ @NotNull Sdk sdk,
+ @Nullable Listener listener,
+ @NotNull List<PyPackage> packages) {
+ super(project, "Uninstalling packages", listener);
+ mySdk = sdk;
+ myPackages = packages;
+ }
+
+ @NotNull
+ @Override
+ protected List<PyExternalProcessException> runTask(@NotNull ProgressIndicator indicator) {
+ final PyPackageManager manager = PyPackageManagers.getInstance().forSdk(mySdk);
+ try {
+ manager.uninstall(myPackages);
+ return Arrays.asList();
+ }
+ catch (PyExternalProcessException e) {
+ return Arrays.asList(e);
+ }
+ finally {
+ manager.refresh();
+ }
+ }
+
+ @NotNull
+ @Override
+ protected String getSuccessTitle() {
+ return "Packages uninstalled successfully";
+ }
+
+ @NotNull
+ @Override
+ protected String getSuccessDescription() {
+ final String packagesString = StringUtil.join(myPackages, new Function<PyPackage, String>() {
+ @Override
+ public String fun(PyPackage pkg) {
+ return "'" + pkg.getName() + "'";
+ }
+ }, ", ");
+ return "Uninstalled packages: " + packagesString;
+ }
+
+ @NotNull
+ @Override
+ protected String getFailureTitle() {
+ return "Uninstall packages failed";
+ }
+ }
+
+ public static String createDescription(List<PyExternalProcessException> exceptions, String firstLine) {
+ final StringBuilder b = new StringBuilder();
+ b.append(firstLine);
+ b.append("\n\n");
+ for (PyExternalProcessException exception : exceptions) {
+ b.append(exception.toString());
+ b.append("\n");
+ }
+ return b.toString();
+ }
+}
diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java b/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java
index 054e9e01d23f..e68cede3f799 100644
--- a/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java
+++ b/python/src/com/jetbrains/python/packaging/PyPackageManagersImpl.java
@@ -15,13 +15,11 @@
*/
package com.jetbrains.python.packaging;
-import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
+import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
/**
@@ -36,22 +34,14 @@ public class PyPackageManagersImpl extends PyPackageManagers {
final String name = sdk.getName();
PyPackageManagerImpl manager = myInstances.get(name);
if (manager == null) {
- manager = new PyPackageManagerImpl(sdk);
+ if (PythonSdkType.isRemote(sdk)) {
+ manager = new PyRemotePackageManagerImpl(sdk);
+ }
+ else {
+ manager = new PyPackageManagerImpl(sdk);
+ }
myInstances.put(name, manager);
}
return manager;
}
-
- @Nullable
- @Override
- public List<PyRequirement> getRequirements(Module module) {
- return PyPackageManagerImpl.getRequirements(module);
- }
-
-
- @Nullable
- @Override
- public List<PyRequirement> getRequirementsFromTxt(Module module) {
- return PyPackageManagerImpl.getRequirementsFromTxt(module);
- }
}
diff --git a/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java b/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java
new file mode 100644
index 000000000000..6c6de77d9454
--- /dev/null
+++ b/python/src/com/jetbrains/python/packaging/PyRemotePackageManagerImpl.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.packaging;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.process.ProcessOutput;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.SdkAdditionalData;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.remote.RemoteFile;
+import com.intellij.remote.RemoteSdkAdditionalData;
+import com.intellij.remote.RemoteSdkCredentials;
+import com.intellij.remote.VagrantNotStartedException;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.PathMappingSettings;
+import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
+import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author vlan
+ */
+public class PyRemotePackageManagerImpl extends PyPackageManagerImpl {
+ private static final String LAUNCH_VAGRANT = "launchVagrant";
+ public static final int ERROR_VAGRANT_NOT_LAUNCHED = 101;
+ public static final int ERROR_REMOTE_ACCESS = 102;
+
+ private static final Logger LOG = Logger.getInstance(PyRemotePackageManagerImpl.class);
+
+ PyRemotePackageManagerImpl(@NotNull Sdk sdk) {
+ super(sdk);
+ }
+
+ @Nullable
+ @Override
+ protected String getHelperPath(String helper) {
+ final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData();
+ if (sdkData instanceof PyRemoteSdkAdditionalDataBase) {
+ final PyRemoteSdkAdditionalDataBase remoteSdkData = (PyRemoteSdkAdditionalDataBase)mySdk.getSdkAdditionalData();
+ try {
+ final RemoteSdkCredentials remoteSdkCredentials = remoteSdkData.getRemoteSdkCredentials(false);
+ if (!StringUtil.isEmpty(remoteSdkCredentials.getHelpersPath())) {
+ return new RemoteFile(remoteSdkCredentials.getHelpersPath(), helper).getPath();
+ }
+ else {
+ return null;
+ }
+ }
+ catch (Exception e) {
+ LOG.error(e);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected ProcessOutput getProcessOutput(@NotNull String helperPath,
+ @NotNull List<String> args,
+ boolean askForSudo,
+ @Nullable String workingDir) throws PyExternalProcessException {
+ final String homePath = mySdk.getHomePath();
+ if (homePath == null) {
+ throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Cannot find interpreter for SDK");
+ }
+ final SdkAdditionalData sdkData = mySdk.getSdkAdditionalData();
+ if (sdkData instanceof PyRemoteSdkAdditionalDataBase) { //remote interpreter
+ RemoteSdkCredentials remoteSdkCredentials;
+ try {
+ remoteSdkCredentials = ((RemoteSdkAdditionalData)sdkData).getRemoteSdkCredentials(false);
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ remoteSdkCredentials = null;
+ }
+ catch (final ExecutionException e) {
+ if (e.getCause() instanceof VagrantNotStartedException) {
+ throw new PyExternalProcessException(ERROR_VAGRANT_NOT_LAUNCHED, helperPath, args, "Vagrant instance is down. <a href=\"" +
+ LAUNCH_VAGRANT +
+ "\">Launch vagrant</a>")
+ .withHandler(LAUNCH_VAGRANT, new Runnable() {
+ @Override
+ public void run() {
+ final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
+ if (manager != null) {
+
+ try {
+ manager.runVagrant(((VagrantNotStartedException)e.getCause()).getVagrantFolder());
+ clearCaches();
+ }
+ catch (ExecutionException e1) {
+ throw new RuntimeException(e1);
+ }
+ }
+ }
+ });
+ }
+ else {
+ throw new PyExternalProcessException(ERROR_REMOTE_ACCESS, helperPath, args, e.getMessage());
+ }
+ }
+ final PythonRemoteInterpreterManager manager = PythonRemoteInterpreterManager.getInstance();
+ if (manager != null && remoteSdkCredentials != null) {
+ final List<String> cmdline = new ArrayList<String>();
+ cmdline.add(homePath);
+ cmdline.add(RemoteFile.detectSystemByPath(homePath).createRemoteFile(helperPath).getPath());
+ cmdline.addAll(Collections2.transform(args, new Function<String, String>() {
+ @Override
+ public String apply(@Nullable String input) {
+ return quoteIfNeeded(input);
+ }
+ }));
+ try {
+ if (askForSudo) {
+ askForSudo = !manager.ensureCanWrite(null, remoteSdkCredentials, remoteSdkCredentials.getInterpreterPath());
+ }
+ ProcessOutput processOutput;
+ do {
+ PathMappingSettings mappings = manager.setupMappings(null, (PyRemoteSdkAdditionalDataBase)sdkData, null);
+ processOutput =
+ manager.runRemoteProcess(null, remoteSdkCredentials, mappings, ArrayUtil.toStringArray(cmdline), workingDir, askForSudo);
+ if (askForSudo && processOutput.getStderr().contains("sudo: 3 incorrect password attempts")) {
+ continue;
+ }
+ break;
+ }
+ while (true);
+ return processOutput;
+ }
+ catch (ExecutionException e) {
+ throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Error running SDK: " + e.getMessage(), e);
+ }
+ }
+ else {
+ throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args,
+ PythonRemoteInterpreterManager.WEB_DEPLOYMENT_PLUGIN_IS_DISABLED);
+ }
+ }
+ else {
+ throw new PyExternalProcessException(ERROR_INVALID_SDK, helperPath, args, "Invalid remote SDK");
+ }
+ }
+
+ @Override
+ protected void subscribeToLocalChanges(Sdk sdk) {
+ // Local VFS changes aren't needed
+ }
+
+ @Override
+ protected void installManagement(@NotNull String name) throws PyExternalProcessException {
+ super.installManagement(name);
+ // TODO: remove temp directory for remote interpreter
+ }
+
+ private static String quoteIfNeeded(String arg) {
+ return arg.replace("<", "\\<").replace(">", "\\>"); //TODO: move this logic to ParametersListUtil.encode
+ }
+}
diff --git a/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java b/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java
index 348d2817ec75..094149336d5d 100644
--- a/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java
+++ b/python/src/com/jetbrains/python/packaging/ui/PyInstalledPackagesPanel.java
@@ -25,10 +25,8 @@ import com.intellij.util.Consumer;
import com.intellij.webcore.packaging.InstalledPackage;
import com.intellij.webcore.packaging.InstalledPackagesPanel;
import com.intellij.webcore.packaging.PackagesNotificationPanel;
-import com.jetbrains.python.packaging.PyExternalProcessException;
-import com.jetbrains.python.packaging.PyPackage;
-import com.jetbrains.python.packaging.PyPackageManager;
-import com.jetbrains.python.packaging.PyPackageManagerImpl;
+import com.jetbrains.python.packaging.*;
+import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.flavors.IronPythonSdkFlavor;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
@@ -42,33 +40,13 @@ import java.util.Set;
* @author yole
*/
public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
- public static final String INSTALL_SETUPTOOLS = "installSetuptools";
- public static final String INSTALL_PIP = "installPip";
+ public static final String INSTALL_MANAGEMENT = "installManagement";
public static final String CREATE_VENV = "createVEnv";
- private boolean myHasSetuptools;
- private boolean myHasPip = true;
+ private boolean myHasManagement = false;
public PyInstalledPackagesPanel(Project project, PackagesNotificationPanel area) {
super(project, area);
- myNotificationArea.addLinkHandler(INSTALL_SETUPTOOLS, new Runnable() {
- @Override
- public void run() {
- final Sdk sdk = getSelectedSdk();
- if (sdk != null) {
- installManagementTool(sdk, PyPackageManagerImpl.SETUPTOOLS);
- }
- }
- });
- myNotificationArea.addLinkHandler(INSTALL_PIP, new Runnable() {
- @Override
- public void run() {
- final Sdk sdk = getSelectedSdk();
- if (sdk != null) {
- installManagementTool(sdk, PyPackageManagerImpl.PIP);
- }
- }
- });
}
private Sdk getSelectedSdk() {
@@ -85,19 +63,8 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
application.executeOnPooledThread(new Runnable() {
@Override
public void run() {
- PyExternalProcessException exc = null;
- try {
- PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(selectedSdk);
- myHasSetuptools = packageManager.findInstalledPackage(PyPackageManagerImpl.PACKAGE_SETUPTOOLS) != null;
- if (!myHasSetuptools) {
- myHasSetuptools = packageManager.findInstalledPackage(PyPackageManagerImpl.PACKAGE_DISTRIBUTE) != null;
- }
- myHasPip = packageManager.findInstalledPackage(PyPackageManagerImpl.PACKAGE_PIP) != null;
- }
- catch (PyExternalProcessException e) {
- exc = e;
- }
- final PyExternalProcessException externalProcessException = exc;
+ PyPackageManager packageManager = PyPackageManager.getInstance(selectedSdk);
+ myHasManagement = packageManager.hasManagement(false);
application.invokeLater(new Runnable() {
@Override
public void run() {
@@ -112,42 +79,24 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
myNotificationArea.hide();
if (!invalid) {
String text = null;
- if (externalProcessException != null) {
- final int retCode = externalProcessException.getRetcode();
- if (retCode == PyPackageManagerImpl.ERROR_NO_PIP) {
- myHasPip = false;
- }
- else if (retCode == PyPackageManagerImpl.ERROR_NO_SETUPTOOLS) {
- myHasSetuptools = false;
- }
- else {
- text = externalProcessException.getMessage();
- }
- final boolean hasPackagingTools = myHasPip && myHasSetuptools;
- allowCreateVirtualEnv &= !hasPackagingTools;
-
- if (externalProcessException.hasHandler()) {
- final String key = externalProcessException.getHandler().first;
- myNotificationArea.addLinkHandler(key,
- new Runnable() {
- @Override
- public void run() {
- externalProcessException.getHandler().second.run();
- myNotificationArea.removeLinkHandler(key);
- updateNotifications(selectedSdk);
+ if (!myHasManagement) {
+ myNotificationArea.addLinkHandler(INSTALL_MANAGEMENT,
+ new Runnable() {
+ @Override
+ public void run() {
+ final Sdk sdk = getSelectedSdk();
+ if (sdk != null) {
+ installManagementTools(sdk);
}
+ myNotificationArea.removeLinkHandler(INSTALL_MANAGEMENT);
+ updateNotifications(selectedSdk);
}
- );
- }
+ }
+ );
}
- if (text == null) {
- if (!myHasSetuptools) {
- text = "Python package management tools not found. <a href=\"" + INSTALL_SETUPTOOLS + "\">Install 'setuptools'</a>";
- }
- else if (!myHasPip) {
- text = "Python packaging tool 'pip' not found. <a href=\"" + INSTALL_PIP + "\">Install 'pip'</a>";
- }
+ if (!myHasManagement) {
+ text = "Python packaging tools not found. <a href=\"" + INSTALL_MANAGEMENT + "\">Install packaging tools</a>";
}
if (text != null) {
if (allowCreateVirtualEnv) {
@@ -157,7 +106,7 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
}
}
- myInstallButton.setEnabled(!invalid && externalProcessException == null && myHasPip);
+ myInstallButton.setEnabled(!invalid && myHasManagement);
}
}
}, ModalityState.any());
@@ -170,8 +119,8 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
return Sets.newHashSet("pip", "distutils", "setuptools");
}
- private void installManagementTool(@NotNull final Sdk sdk, final String name) {
- final PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, sdk, new PyPackageManagerImpl.UI.Listener() {
+ private void installManagementTools(@NotNull final Sdk sdk) {
+ final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, sdk, new PyPackageManagerUI.Listener() {
@Override
public void started() {
myPackagesTable.setPaintBusy(true);
@@ -180,11 +129,11 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
@Override
public void finished(List<PyExternalProcessException> exceptions) {
myPackagesTable.setPaintBusy(false);
- PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk);
+ PyPackageManager packageManager = PyPackageManager.getInstance(sdk);
if (!exceptions.isEmpty()) {
- final String firstLine = "Install package failed. ";
- final String description = PyPackageManagerImpl.UI.createDescription(exceptions, firstLine);
- packageManager.showInstallationError(myProject, "Failed to install " + name, description);
+ final String firstLine = "Install Python packaging tools failed. ";
+ final String description = PyPackageManagerUI.createDescription(exceptions, firstLine);
+ PackagesNotificationPanel.showError(myProject, "Failed to install Python packaging tools", description);
}
packageManager.refresh();
updatePackages(new PyPackageManagementService(myProject, sdk));
@@ -194,22 +143,22 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
updateNotifications(sdk);
}
});
- ui.installManagement(name);
+ ui.installManagement();
}
@Override
protected boolean canUninstallPackage(InstalledPackage pkg) {
- if (!myHasPip) return false;
+ if (!myHasManagement) return false;
if (PythonSdkType.isVirtualEnv(getSelectedSdk()) && pkg instanceof PyPackage) {
final String location = ((PyPackage)pkg).getLocation();
- if (location != null && location.startsWith(PyPackageManagerImpl.getUserSite())) {
+ if (location != null && location.startsWith(PySdkUtil.getUserSite())) {
return false;
}
}
final String name = pkg.getName();
- if (PyPackageManagerImpl.PACKAGE_PIP.equals(name) ||
- PyPackageManagerImpl.PACKAGE_SETUPTOOLS.equals(name) ||
- PyPackageManagerImpl.PACKAGE_DISTRIBUTE.equals(name)) {
+ if (PyPackageManager.PACKAGE_PIP.equals(name) ||
+ PyPackageManager.PACKAGE_SETUPTOOLS.equals(name) ||
+ PyPackageManager.PACKAGE_DISTRIBUTE.equals(name)) {
return false;
}
return true;
@@ -217,6 +166,6 @@ public class PyInstalledPackagesPanel extends InstalledPackagesPanel {
@Override
protected boolean canUpgradePackage(InstalledPackage pyPackage) {
- return myHasPip;
+ return myHasManagement;
}
}
diff --git a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java
index ffd291a995b1..95330b361dfb 100644
--- a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java
+++ b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java
@@ -23,6 +23,7 @@ import com.intellij.webcore.packaging.InstalledPackage;
import com.intellij.webcore.packaging.PackageManagementService;
import com.intellij.webcore.packaging.RepoPackage;
import com.jetbrains.python.packaging.*;
+import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import org.apache.xmlrpc.AsyncCallback;
import org.jetbrains.annotations.NonNls;
@@ -112,7 +113,7 @@ public class PyPackageManagementService extends PackageManagementService {
public String getInstallToUserText() {
String userSiteText = "Install to user's site packages directory";
if (!PythonSdkType.isRemote(mySdk))
- userSiteText += " (" + PyPackageManagerImpl.getUserSite() + ")";
+ userSiteText += " (" + PySdkUtil.getUserSite() + ")";
return userSiteText;
}
@@ -130,12 +131,12 @@ public class PyPackageManagementService extends PackageManagementService {
public Collection<InstalledPackage> getInstalledPackages() throws IOException {
List<PyPackage> packages;
try {
- packages = ((PyPackageManagerImpl)PyPackageManager.getInstance(mySdk)).getPackages();
+ packages = PyPackageManager.getInstance(mySdk).getPackages(false);
}
catch (PyExternalProcessException e) {
throw new IOException(e);
}
- return new ArrayList<InstalledPackage>(packages);
+ return packages != null ? new ArrayList<InstalledPackage>(packages) : new ArrayList<InstalledPackage>();
}
@Override
@@ -145,7 +146,7 @@ public class PyPackageManagementService extends PackageManagementService {
final String repository = PyPIPackageUtil.PYPI_URL.equals(repoPackage.getRepoUrl()) ? null : repoPackage.getRepoUrl();
final List<String> extraArgs = new ArrayList<String>();
if (installToUser) {
- extraArgs.add(PyPackageManagerImpl.USE_USER_SITE);
+ extraArgs.add(PyPackageManager.USE_USER_SITE);
}
if (extraOptions != null) {
// TODO: Respect arguments quotation
@@ -166,7 +167,7 @@ public class PyPackageManagementService extends PackageManagementService {
req = new PyRequirement(packageName);
}
- final PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, mySdk, new PyPackageManagerImpl.UI.Listener() {
+ final PyPackageManagerUI ui = new PyPackageManagerUI(myProject, mySdk, new PyPackageManagerUI.Listener() {
@Override
public void started() {
listener.operationStarted(packageName);
@@ -183,7 +184,7 @@ public class PyPackageManagementService extends PackageManagementService {
private String toErrorDescription(List<PyExternalProcessException> exceptions) {
String errorDescription = null;
if (exceptions != null && exceptions.size() > 0) {
- errorDescription = PyPackageManagerImpl.UI.createDescription(exceptions, "");
+ errorDescription = PyPackageManagerUI.createDescription(exceptions, "");
}
return errorDescription;
}
@@ -191,7 +192,7 @@ public class PyPackageManagementService extends PackageManagementService {
@Override
public void uninstallPackages(List<InstalledPackage> installedPackages, final Listener listener) {
final String packageName = installedPackages.size() == 1 ? installedPackages.get(0).getName() : null;
- PyPackageManagerImpl.UI ui = new PyPackageManagerImpl.UI(myProject, mySdk, new PyPackageManagerImpl.UI.Listener() {
+ PyPackageManagerUI ui = new PyPackageManagerUI(myProject, mySdk, new PyPackageManagerUI.Listener() {
@Override
public void started() {
listener.operationStarted(packageName);
diff --git a/python/src/com/jetbrains/python/projectView/PyElementNode.java b/python/src/com/jetbrains/python/projectView/PyElementNode.java
index 20c7b811b8a6..6ec347236773 100644
--- a/python/src/com/jetbrains/python/projectView/PyElementNode.java
+++ b/python/src/com/jetbrains/python/projectView/PyElementNode.java
@@ -19,11 +19,14 @@ import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.impl.nodes.BasePsiNode;
import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.project.Project;
+import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFunction;
+import javax.swing.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -57,14 +60,23 @@ public class PyElementNode extends BasePsiNode<PyElement> {
@Override
protected void updateImpl(PresentationData data) {
- PyElement value = getValue();
+ final PyElement value = getValue();
final String name = value.getName();
- StringBuilder presentableText = new StringBuilder(name != null ? name : "<unnamed>");
- if (value instanceof PyFunction) {
- presentableText.append(((PyFunction) value).getParameterList().getPresentableText(false));
+ final ItemPresentation presentation = value.getPresentation();
+ String presentableText = name != null ? name : PyNames.UNNAMED_ELEMENT;
+ Icon presentableIcon = value.getIcon(0);
+ if (presentation != null) {
+ final String text = presentation.getPresentableText();
+ if (text != null) {
+ presentableText = text;
+ }
+ final Icon icon = presentation.getIcon(false);
+ if (icon != null) {
+ presentableIcon = icon;
+ }
}
- data.setPresentableText(presentableText.toString());
- data.setIcon(value.getIcon(0));
+ data.setPresentableText(presentableText);
+ data.setIcon(presentableIcon);
}
@Override
diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java
index cd4e582bb6f2..dc5cbf4917a5 100644
--- a/python/src/com/jetbrains/python/psi/PyUtil.java
+++ b/python/src/com/jetbrains/python/psi/PyUtil.java
@@ -1394,8 +1394,8 @@ public class PyUtil {
@Nullable
public static PsiElement findPrevAtOffset(PsiFile psiFile, int caretOffset, Class... toSkip) {
- PsiElement element = psiFile.findElementAt(caretOffset);
- if (element == null || caretOffset < 0) {
+ PsiElement element;
+ if (caretOffset < 0) {
return null;
}
int lineStartOffset = 0;
diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java
index bb2104fc0c77..611cbc84ad2e 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java
@@ -17,6 +17,7 @@ package com.jetbrains.python.psi.impl;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.lang.ASTNode;
+import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.NotNullLazyValue;
@@ -53,10 +54,13 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
+import static com.intellij.openapi.util.text.StringUtil.join;
+import static com.intellij.openapi.util.text.StringUtil.notNullize;
+
/**
* @author yole
*/
-public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implements PyClass {
+public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyClass {
public static final PyClass[] EMPTY_ARRAY = new PyClassImpl[0];
private List<PyTargetExpression> myInstanceAttributes;
@@ -288,6 +292,32 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
return result.toArray(new PyClass[result.size()]);
}
+ @Override
+ public ItemPresentation getPresentation() {
+ return new PyElementPresentation(this) {
+ @Nullable
+ @Override
+ public String getPresentableText() {
+ if (!isValid()) {
+ return null;
+ }
+ final StringBuilder result = new StringBuilder(notNullize(getName(), PyNames.UNNAMED_ELEMENT));
+ final PyExpression[] superClassExpressions = getSuperClassExpressions();
+ if (superClassExpressions.length > 0) {
+ result.append("(");
+ result.append(join(Arrays.asList(superClassExpressions), new Function<PyExpression, String>() {
+ public String fun(PyExpression expr) {
+ String name = expr.getText();
+ return notNullize(name, PyNames.UNNAMED_ELEMENT);
+ }
+ }, ", "));
+ result.append(")");
+ }
+ return result.toString();
+ }
+ };
+ }
+
@NotNull
private static List<PyClassLikeType> mroMerge(@NotNull List<List<PyClassLikeType>> sequences) {
List<PyClassLikeType> result = new LinkedList<PyClassLikeType>(); // need to insert to 0th position on linearize
diff --git a/python/src/com/jetbrains/python/psi/impl/PyElementPresentation.java b/python/src/com/jetbrains/python/psi/impl/PyElementPresentation.java
new file mode 100644
index 000000000000..7888071c7321
--- /dev/null
+++ b/python/src/com/jetbrains/python/psi/impl/PyElementPresentation.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.psi.impl;
+
+import com.intellij.navigation.ColoredItemPresentation;
+import com.intellij.openapi.editor.colors.TextAttributesKey;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.python.PyNames;
+import com.jetbrains.python.psi.PyElement;
+import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+/**
+* @author vlan
+*/
+public class PyElementPresentation implements ColoredItemPresentation {
+ @NotNull private final PyElement myElement;
+
+ public PyElementPresentation(@NotNull PyElement element) {
+ myElement = element;
+ }
+
+ @Nullable
+ @Override
+ public TextAttributesKey getTextAttributesKey() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getPresentableText() {
+ final String name = myElement.getName();
+ return name != null ? name : PyNames.UNNAMED_ELEMENT;
+ }
+
+ @Nullable
+ @Override
+ public String getLocationString() {
+ return "(" + getPackageForFile(myElement.getContainingFile()) + ")";
+ }
+
+ @Nullable
+ @Override
+ public Icon getIcon(boolean unused) {
+ return myElement.getIcon(0);
+ }
+
+ public static String getPackageForFile(@NotNull PsiFile containingFile) {
+ final VirtualFile vFile = containingFile.getVirtualFile();
+
+ if (vFile != null) {
+ final String importableName = QualifiedNameFinder.findShortestImportableName(containingFile, vFile);
+ if (importableName != null) {
+ return importableName;
+ }
+ }
+ return "";
+ }
+}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java
index 86f84abb7fb6..2c1842f3337f 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java
@@ -16,6 +16,7 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
+import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
@@ -54,6 +55,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
+import static com.intellij.openapi.util.text.StringUtil.notNullize;
import static com.jetbrains.python.psi.PyFunction.Modifier.CLASSMETHOD;
import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD;
import static com.jetbrains.python.psi.impl.PyCallExpressionHelper.interpretAsModifierWrappingCall;
@@ -61,7 +63,7 @@ import static com.jetbrains.python.psi.impl.PyCallExpressionHelper.interpretAsMo
/**
* Implements PyFunction.
*/
-public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> implements PyFunction {
+public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements PyFunction {
public PyFunctionImpl(ASTNode astNode) {
super(astNode);
@@ -259,6 +261,17 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
return type;
}
+ @Override
+ public ItemPresentation getPresentation() {
+ return new PyElementPresentation(this) {
+ @Nullable
+ @Override
+ public String getPresentableText() {
+ return notNullize(getName(), PyNames.UNNAMED_ELEMENT) + getParameterList().getPresentableText(true);
+ }
+ };
+ }
+
@Nullable
private PyType replaceSelf(@Nullable PyType returnType, @Nullable PyExpression receiver, @NotNull TypeEvalContext context) {
if (receiver != null) {
@@ -292,32 +305,30 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
final PyBuiltinCache cache = PyBuiltinCache.getInstance(this);
final PyStatementList statements = getStatementList();
final Set<PyType> types = new LinkedHashSet<PyType>();
- if (statements != null) {
- statements.accept(new PyRecursiveElementVisitor() {
- @Override
- public void visitPyYieldExpression(PyYieldExpression node) {
- final PyType type = context.getType(node);
- if (node.isDelegating() && type instanceof PyCollectionType) {
- final PyCollectionType collectionType = (PyCollectionType)type;
- types.add(collectionType.getElementType(context));
- }
- else {
- types.add(type);
- }
+ statements.accept(new PyRecursiveElementVisitor() {
+ @Override
+ public void visitPyYieldExpression(PyYieldExpression node) {
+ final PyType type = context.getType(node);
+ if (node.isDelegating() && type instanceof PyCollectionType) {
+ final PyCollectionType collectionType = (PyCollectionType)type;
+ types.add(collectionType.getElementType(context));
}
-
- @Override
- public void visitPyFunction(PyFunction node) {
- // Ignore nested functions
+ else {
+ types.add(type);
}
- });
- final int n = types.size();
- if (n == 1) {
- elementType = Ref.create(types.iterator().next());
}
- else if (n > 0) {
- elementType = Ref.create(PyUnionType.union(types));
+
+ @Override
+ public void visitPyFunction(PyFunction node) {
+ // Ignore nested functions
}
+ });
+ final int n = types.size();
+ if (n == 1) {
+ elementType = Ref.create(types.iterator().next());
+ }
+ else if (n > 0) {
+ elementType = Ref.create(PyUnionType.union(types));
}
if (elementType != null) {
final PyClass generator = cache.getClass(PyNames.FAKE_GENERATOR);
@@ -335,14 +346,12 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
public PyType getReturnStatementType(TypeEvalContext typeEvalContext) {
ReturnVisitor visitor = new ReturnVisitor(this, typeEvalContext);
final PyStatementList statements = getStatementList();
- if (statements != null) {
- statements.accept(visitor);
- if (isGeneratedStub() && !visitor.myHasReturns) {
- if (PyNames.INIT.equals(getName())) {
- return PyNoneType.INSTANCE;
- }
- return null;
+ statements.accept(visitor);
+ if (isGeneratedStub() && !visitor.myHasReturns) {
+ if (PyNames.INIT.equals(getName())) {
+ return PyNoneType.INSTANCE;
}
+ return null;
}
return visitor.result();
}
@@ -376,9 +385,6 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
@Nullable
public String extractDeprecationMessage() {
PyStatementList statementList = getStatementList();
- if (statementList == null) {
- return null;
- }
return extractDeprecationMessage(Arrays.asList(statementList.getStatements()));
}
@@ -430,7 +436,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
@Nullable
@Override
public StructuredDocString getStructuredDocString() {
- return CachedValuesManager.getManager(getProject()).getCachedValue(this, myCachedStructuredDocStringProvider);
+ return CachedValuesManager.getCachedValue(this, myCachedStructuredDocStringProvider);
}
private boolean isGeneratedStub() {
@@ -527,15 +533,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
public PyStringLiteralExpression getDocStringExpression() {
final PyStatementList stmtList = getStatementList();
- return stmtList != null ? DocStringUtil.findDocStringExpression(stmtList) : null;
- }
-
- protected String getElementLocation() {
- final PyClass containingClass = getContainingClass();
- if (containingClass != null) {
- return "(" + containingClass.getName() + " in " + getPackageForFile(getContainingFile()) + ")";
- }
- return super.getElementLocation();
+ return DocStringUtil.findDocStringExpression(stmtList);
}
@NotNull
diff --git a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java
index cc9178c07f9e..64108441c347 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyNamedParameterImpl.java
@@ -16,6 +16,7 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
+import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -48,7 +49,7 @@ import java.util.Map;
/**
* @author yole
*/
-public class PyNamedParameterImpl extends PyPresentableElementImpl<PyNamedParameterStub> implements PyNamedParameter {
+public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub> implements PyNamedParameter {
public PyNamedParameterImpl(ASTNode astNode) {
super(astNode);
}
@@ -280,6 +281,11 @@ public class PyNamedParameterImpl extends PyPresentableElementImpl<PyNamedParame
return null;
}
+ @Override
+ public ItemPresentation getPresentation() {
+ return new PyElementPresentation(this);
+ }
+
private static void processLocalCalls(@NotNull PyFunction function, @NotNull Processor<PyCallExpression> processor) {
final PsiFile file = function.getContainingFile();
final String name = function.getName();
diff --git a/python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java
deleted file mode 100644
index d04d04062651..000000000000
--- a/python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2000-2013 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.python.psi.impl;
-
-import com.intellij.lang.ASTNode;
-import com.intellij.navigation.ItemPresentation;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiNamedElement;
-import com.intellij.psi.stubs.IStubElementType;
-import com.intellij.psi.stubs.StubElement;
-import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
-
-import javax.swing.*;
-
-/**
- * @author yole
- */
-public abstract class PyPresentableElementImpl<T extends StubElement> extends PyBaseElementImpl<T> implements PsiNamedElement {
- public PyPresentableElementImpl(ASTNode astNode) {
- super(astNode);
- }
-
- protected PyPresentableElementImpl(final T stub, final IStubElementType nodeType) {
- super(stub, nodeType);
- }
-
- public ItemPresentation getPresentation() {
- return new ItemPresentation() {
- public String getPresentableText() {
- final String name = getName();
- return name != null ? name : "<none>";
- }
-
- public String getLocationString() {
- return getElementLocation();
- }
-
- public Icon getIcon(final boolean open) {
- return PyPresentableElementImpl.this.getIcon(0);
- }
- };
- }
-
- protected String getElementLocation() {
- return "(" + getPackageForFile(getContainingFile()) + ")";
- }
-
- public static String getPackageForFile(final PsiFile containingFile) {
- final VirtualFile vFile = containingFile.getVirtualFile();
-
- if (vFile != null) {
- final String importableName = QualifiedNameFinder.findShortestImportableName(containingFile, vFile);
- if (importableName != null) {
- return importableName;
- }
- }
- return "";
- }
-}
diff --git a/python/src/com/jetbrains/python/psi/impl/PySingleStarParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PySingleStarParameterImpl.java
index 70a46fbe39f9..44a01759f403 100644
--- a/python/src/com/jetbrains/python/psi/impl/PySingleStarParameterImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PySingleStarParameterImpl.java
@@ -16,21 +16,18 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiElement;
-import com.intellij.util.IncorrectOperationException;
+import com.intellij.navigation.ItemPresentation;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.PySingleStarParameter;
import com.jetbrains.python.psi.PyTupleParameter;
import com.jetbrains.python.psi.stubs.PySingleStarParameterStub;
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
/**
* @author yole
*/
-public class PySingleStarParameterImpl extends PyPresentableElementImpl<PySingleStarParameterStub> implements PySingleStarParameter {
+public class PySingleStarParameterImpl extends PyBaseElementImpl<PySingleStarParameterStub> implements PySingleStarParameter {
public PySingleStarParameterImpl(ASTNode astNode) {
super(astNode);
}
@@ -39,22 +36,22 @@ public class PySingleStarParameterImpl extends PyPresentableElementImpl<PySingle
super(stub, PyElementTypes.SINGLE_STAR_PARAMETER);
}
- public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
- throw new UnsupportedOperationException();
- }
-
+ @Override
public PyNamedParameter getAsNamed() {
return null;
}
+ @Override
public PyTupleParameter getAsTuple() {
return null;
}
+ @Override
public PyExpression getDefaultValue() {
return null;
}
+ @Override
public boolean hasDefaultValue() {
return false;
}
@@ -63,4 +60,9 @@ public class PySingleStarParameterImpl extends PyPresentableElementImpl<PySingle
public boolean isSelf() {
return false;
}
+
+ @Override
+ public ItemPresentation getPresentation() {
+ return new PyElementPresentation(this);
+ }
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java
index bf519737f0f4..084aeecc3df0 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyStringLiteralExpressionImpl.java
@@ -327,7 +327,7 @@ public class PyStringLiteralExpressionImpl extends PyElementImpl implements PySt
@Nullable
@Override
public String getLocationString() {
- return "(" + PyPresentableElementImpl.getPackageForFile(getContainingFile()) + ")";
+ return "(" + PyElementPresentation.getPackageForFile(getContainingFile()) + ")";
}
@Nullable
diff --git a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java
index 3805c8df525e..d125190b77e9 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyTargetExpressionImpl.java
@@ -16,6 +16,7 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
+import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
@@ -61,7 +62,7 @@ import java.util.List;
/**
* @author yole
*/
-public class PyTargetExpressionImpl extends PyPresentableElementImpl<PyTargetExpressionStub> implements PyTargetExpression {
+public class PyTargetExpressionImpl extends PyBaseElementImpl<PyTargetExpressionStub> implements PyTargetExpression {
QualifiedName myQualifiedName;
public PyTargetExpressionImpl(ASTNode astNode) {
@@ -659,12 +660,19 @@ public class PyTargetExpressionImpl extends PyPresentableElementImpl<PyTargetExp
return null;
}
- protected String getElementLocation() {
- final PyClass containingClass = getContainingClass();
- if (containingClass != null) {
- return "(" + containingClass.getName() + " in " + getPackageForFile(getContainingFile()) + ")";
- }
- return super.getElementLocation();
+ @Override
+ public ItemPresentation getPresentation() {
+ return new PyElementPresentation(this) {
+ @Nullable
+ @Override
+ public String getLocationString() {
+ final PyClass containingClass = getContainingClass();
+ if (containingClass != null) {
+ return "(" + containingClass.getName() + " in " + getPackageForFile(getContainingFile()) + ")";
+ }
+ return super.getLocationString();
+ }
+ };
}
@Nullable
diff --git a/python/src/com/jetbrains/python/psi/impl/PyTupleParameterImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTupleParameterImpl.java
index 09e097cb0e99..9935841c33bb 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyTupleParameterImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyTupleParameterImpl.java
@@ -16,19 +16,17 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
-import com.intellij.psi.PsiElement;
-import com.intellij.util.IncorrectOperationException;
+import com.intellij.navigation.ItemPresentation;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PythonDialectsTokenSetProvider;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.stubs.PyTupleParameterStub;
-import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
/**
* Represents a tuple parameter as stubbed element.
*/
-public class PyTupleParameterImpl extends PyPresentableElementImpl<PyTupleParameterStub> implements PyTupleParameter {
+public class PyTupleParameterImpl extends PyBaseElementImpl<PyTupleParameterStub> implements PyTupleParameter {
public PyTupleParameterImpl(ASTNode astNode) {
super(astNode);
@@ -62,10 +60,6 @@ public class PyTupleParameterImpl extends PyPresentableElementImpl<PyTupleParame
return getDefaultValue() != null;
}
- public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
- throw new IncorrectOperationException("Can't rename a tuple parameter to '" + name +"'");
- }
-
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyTupleParameter(this);
@@ -80,4 +74,9 @@ public class PyTupleParameterImpl extends PyPresentableElementImpl<PyTupleParame
public boolean isSelf() {
return false;
}
+
+ @Override
+ public ItemPresentation getPresentation() {
+ return new PyElementPresentation(this);
+ }
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java b/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java
index 7e15cab6ffda..af767684643e 100644
--- a/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java
+++ b/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java
@@ -19,7 +19,9 @@ import com.intellij.facet.Facet;
import com.intellij.facet.FacetManager;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleType;
@@ -163,7 +165,8 @@ public class PythonLanguageLevelPusher implements FilePropertyPusher<LanguageLev
oStream.close();
for (VirtualFile child : fileOrDir.getChildren()) {
- if (!child.isDirectory() && PythonFileType.INSTANCE.equals(child.getFileType())) {
+ final FileType fileType = FileTypeRegistry.getInstance().getFileTypeByFileName(child.getName());
+ if (!child.isDirectory() && PythonFileType.INSTANCE.equals(fileType)) {
PushedFilePropertiesUpdater.getInstance(project).filePropertiesChanged(child);
}
}
diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java
index 6895249a641f..e360c9e46685 100644
--- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java
+++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureHandler.java
@@ -126,7 +126,7 @@ public class PyChangeSignatureHandler implements ChangeSignatureHandler {
private static boolean isNotUnderSourceRoot(@NotNull final Project project,
@Nullable final PsiFile psiFile,
- @NotNull final Editor editor) {
+ @Nullable final Editor editor) {
if (psiFile == null) return true;
final VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile != null) {
diff --git a/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionDelegate.java b/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionDelegate.java
index cf1b4d19dd4e..8306a4faec23 100644
--- a/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionDelegate.java
+++ b/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionDelegate.java
@@ -39,11 +39,10 @@ import org.jetbrains.annotations.Nullable;
* @author vlan
*/
public class PyMoveClassOrFunctionDelegate extends MoveHandlerDelegate {
-
@Override
public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) {
for (PsiElement element : elements) {
- if (element instanceof PyClass || element instanceof PyFunction) continue;
+ if ((element instanceof PyClass || element instanceof PyFunction) && PyUtil.isTopLevel(element)) continue;
return false;
}
return super.canMove(elements, targetContainer);
diff --git a/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java b/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java
index 39fc2c909cf6..1d0a4bc3ad54 100644
--- a/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java
+++ b/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java
@@ -48,9 +48,9 @@ import com.intellij.ui.components.JBLabel;
import com.intellij.util.NullableConsumer;
import com.intellij.util.PathUtil;
import com.intellij.util.PlatformUtils;
+import com.intellij.webcore.packaging.PackagesNotificationPanel;
import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackageManager;
-import com.jetbrains.python.packaging.PyPackageManagerImpl;
import com.jetbrains.python.packaging.PyPackageService;
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor;
import com.jetbrains.python.ui.IdeaDialog;
@@ -416,7 +416,7 @@ public class CreateVirtualEnvDialog extends IdeaDialog {
String myPath;
public void run(@NotNull final ProgressIndicator indicator) {
- final PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(basicSdk);
+ final PyPackageManager packageManager = PyPackageManager.getInstance(basicSdk);
try {
indicator.setText("Creating virtual environment for " + basicSdk.getName());
myPath = packageManager.createVirtualEnv(getDestination(), useGlobalSitePackages());
@@ -425,7 +425,7 @@ public class CreateVirtualEnvDialog extends IdeaDialog {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
- packageManager.showInstallationError(getOwner(), "Failed to Create Virtual Environment", e.toString());
+ PackagesNotificationPanel.showError(getOwner(), "Failed to Create Virtual Environment", e.toString());
}
}, ModalityState.any());
}
diff --git a/python/src/com/jetbrains/python/sdk/PySdkUtil.java b/python/src/com/jetbrains/python/sdk/PySdkUtil.java
index 8f2b62b1b414..97d5ee545cbb 100644
--- a/python/src/com/jetbrains/python/sdk/PySdkUtil.java
+++ b/python/src/com/jetbrains/python/sdk/PySdkUtil.java
@@ -15,10 +15,13 @@
*/
package com.jetbrains.python.sdk;
+import com.intellij.execution.ExecutionException;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessOutput;
+import com.intellij.execution.util.ExecUtil;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.util.SystemInfo;
@@ -29,8 +32,10 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.remote.RemoteSdkAdditionalData;
-import com.intellij.util.ArrayUtil;
+import com.intellij.util.SystemProperties;
import com.intellij.util.containers.HashMap;
+import com.jetbrains.python.packaging.PyPackageUtil;
+import com.jetbrains.python.packaging.PyRequirement;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -38,8 +43,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -56,6 +60,7 @@ public class PySdkUtil {
// Windows EOF marker, Ctrl+Z
public static final int SUBSTITUTE = 26;
+ public static final String PATH_ENV_VARIABLE = "PATH";
private PySdkUtil() {
// explicitly none
@@ -87,57 +92,29 @@ public class PySdkUtil {
return getProcessOutput(homePath, command, null, timeout);
}
- /**
- * Executes a process and returns its stdout and stderr outputs as lists of lines.
- * Waits for process for possibly limited duration.
- *
- * @param homePath process run directory
- * @param command command to execute and its arguments
- * @param addEnv items are prepended to same-named values of inherited process environment.
- * @param timeout how many milliseconds to wait until the process terminates; non-positive means inifinity.
- * @return a tuple of (stdout lines, stderr lines, exit_code), lines in them have line terminators stripped, or may be null.
- */
@NotNull
public static ProcessOutput getProcessOutput(String homePath,
@NonNls String[] command,
- @Nullable @NonNls String[] addEnv,
+ @Nullable @NonNls Map<String, String> extraEnv,
final int timeout) {
- return getProcessOutput(homePath, command, addEnv, timeout, null, true);
+ return getProcessOutput(homePath, command, extraEnv, timeout, null, true);
}
- /**
- * Executes a process and returns its stdout and stderr outputs as lists of lines.
- * Waits for process for possibly limited duration.
- *
- * @param homePath process run directory
- * @param command command to execute and its arguments
- * @param addEnv items are prepended to same-named values of inherited process environment.
- * @param timeout how many milliseconds to wait until the process terminates; non-positive means infinity.
- * @param stdin the data to write to the process standard input stream
- * @param needEOFMarker
- * @return a tuple of (stdout lines, stderr lines, exit_code), lines in them have line terminators stripped, or may be null.
- */
@NotNull
public static ProcessOutput getProcessOutput(String homePath,
@NonNls String[] command,
- @Nullable @NonNls String[] addEnv,
+ @Nullable @NonNls Map<String, String> extraEnv,
final int timeout,
@Nullable byte[] stdin,
boolean needEOFMarker) {
- final ProcessOutput failureOutput = new ProcessOutput();
if (homePath == null || !new File(homePath).exists()) {
- return failureOutput;
+ return new ProcessOutput();
}
+ final Map<String, String> systemEnv = System.getenv();
+ final Map<String, String> env = extraEnv != null ? mergeEnvVariables(systemEnv, extraEnv) : systemEnv;
try {
- List<String> commands = new ArrayList<String>();
- if (SystemInfo.isWindows && StringUtil.endsWithIgnoreCase(command[0], ".bat")) {
- commands.add("cmd");
- commands.add("/c");
- }
- Collections.addAll(commands, command);
- String[] newEnv = buildAdditionalEnv(addEnv);
- Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(commands), newEnv, new File(homePath));
- CapturingProcessHandler processHandler = new CapturingProcessHandler(process);
+ final Process process = ExecUtil.exec(Arrays.asList(command), homePath, env);
+ final CapturingProcessHandler processHandler = new CapturingProcessHandler(process);
if (stdin != null) {
final OutputStream processInput = processHandler.getProcessInput();
assert processInput != null;
@@ -152,72 +129,61 @@ public class PySdkUtil {
}
return processHandler.runProcess(timeout);
}
- catch (final IOException ex) {
- LOG.warn(ex);
- return new ProcessOutput() {
- @Override
- public String getStderr() {
- String err = super.getStderr();
- if (!StringUtil.isEmpty(err)) {
- err += "\n" + ex.getMessage();
- }
- else {
- err = ex.getMessage();
- }
- return err;
- }
- };
+ catch (ExecutionException e) {
+ return getOutputForException(e);
+ }
+ catch (IOException e) {
+ return getOutputForException(e);
}
}
- private static String[] buildAdditionalEnv(String[] addEnv) {
- String[] newEnv = null;
- if (addEnv != null) {
- Map<String, String> envMap = buildEnvMap(addEnv);
- newEnv = new String[envMap.size()];
- int i = 0;
- for (Map.Entry<String, String> entry : envMap.entrySet()) {
- newEnv[i] = entry.getKey() + "=" + entry.getValue();
- i += 1;
+ private static ProcessOutput getOutputForException(final Exception e) {
+ LOG.warn(e);
+ return new ProcessOutput() {
+ @Override
+ public String getStderr() {
+ String err = super.getStderr();
+ if (!StringUtil.isEmpty(err)) {
+ err += "\n" + e.getMessage();
+ }
+ else {
+ err = e.getMessage();
+ }
+ return err;
}
- }
- return newEnv;
+ };
}
- public static Map<String, String> buildEnvMap(String[] addEnv) {
- Map<String, String> envMap = new HashMap<String, String>(System.getenv());
- // turn additional ent into map
- Map<String, String> addMap = new HashMap<String, String>();
- for (String envItem : addEnv) {
- int pos = envItem.indexOf('=');
- if (pos > 0) {
- String key = envItem.substring(0, pos);
- String value = envItem.substring(pos + 1, envItem.length());
- addMap.put(key, value);
- }
- else {
- LOG.warn(String.format("Invalid env value: '%s'", envItem));
- }
- }
- // fuse old and new
- for (Map.Entry<String, String> entry : addMap.entrySet()) {
- final String key = entry.getKey();
- final String value = entry.getValue();
- final String oldValue = envMap.get(key);
- if (oldValue != null) {
- envMap.put(key, value + oldValue);
+ @NotNull
+ public static Map<String, String> mergeEnvVariables(@NotNull Map<String, String> environment,
+ @NotNull Map<String, String> extraEnvironment) {
+ final Map<String, String> result = new HashMap<String, String>(environment);
+ for (Map.Entry<String, String> entry : extraEnvironment.entrySet()) {
+ if (PATH_ENV_VARIABLE.equals(entry.getKey()) && result.containsKey(PATH_ENV_VARIABLE)) {
+ result.put(PATH_ENV_VARIABLE, result.get(PATH_ENV_VARIABLE) + File.pathSeparator + entry.getValue());
}
else {
- envMap.put(key, value);
+ result.put(entry.getKey(), entry.getValue());
}
}
- return envMap;
+ return result;
}
public static boolean isRemote(@Nullable Sdk sdk) {
return sdk != null && sdk.getSdkAdditionalData() instanceof RemoteSdkAdditionalData;
}
+ public static String getUserSite() {
+ if (SystemInfo.isWindows) {
+ final String appdata = System.getenv("APPDATA");
+ return appdata + File.separator + "Python";
+ }
+ else {
+ final String userHome = SystemProperties.getUserHome();
+ return userHome + File.separator + ".local";
+ }
+ }
+
public static boolean isElementInSkeletons(@NotNull final PsiElement element) {
final PsiFile file = element.getContainingFile();
if (file != null) {
@@ -266,4 +232,13 @@ public class PySdkUtil {
}
return null;
}
+
+ @Nullable
+ public static List<PyRequirement> getRequirementsFromTxt(Module module) {
+ final VirtualFile requirementsTxt = PyPackageUtil.findRequirementsTxt(module);
+ if (requirementsTxt != null) {
+ return PyRequirement.parse(requirementsTxt);
+ }
+ return null;
+ }
}
diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java b/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java
index 923629ce4473..cc6a2c6b6121 100644
--- a/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java
+++ b/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java
@@ -46,7 +46,7 @@ import java.util.List;
import java.util.Set;
public class PythonSdkDetailsStep extends BaseListPopupStep<String> {
- private DialogWrapper myMore;
+ @Nullable private DialogWrapper myMore;
private final Project myProject;
private final Component myOwnerComponent;
private final Sdk[] myExistingSdks;
@@ -120,6 +120,8 @@ public class PythonSdkDetailsStep extends BaseListPopupStep<String> {
}
private void optionSelected(final String selectedValue) {
+ if (!MORE.equals(selectedValue) && myMore != null)
+ Disposer.dispose(myMore.getDisposable());
if (LOCAL.equals(selectedValue)) {
createLocalSdk();
}
@@ -129,7 +131,7 @@ public class PythonSdkDetailsStep extends BaseListPopupStep<String> {
else if (VIRTUALENV.equals(selectedValue)) {
createVirtualEnvSdk();
}
- else {
+ else if (myMore != null) {
myMore.show();
}
}
diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java
index 58de09370d37..460c875491ce 100644
--- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java
+++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.sdk;
+import com.google.common.collect.ImmutableMap;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.ProcessOutput;
@@ -771,16 +772,12 @@ public class PythonSdkType extends SdkType {
}
@NotNull
- public static List<String> getSysPathsFromScript(String bin_path) throws InvalidSdkException {
+ public static List<String> getSysPathsFromScript(@NotNull String binaryPath) throws InvalidSdkException {
String scriptFile = PythonHelpersLocator.getHelperPath("syspath.py");
// to handle the situation when PYTHONPATH contains ., we need to run the syspath script in the
// directory of the script itself - otherwise the dir in which we run the script (e.g. /usr/bin) will be added to SDK path
- String[] add_environment = getVirtualEnvAdditionalEnv(bin_path);
- final ProcessOutput run_result = PySdkUtil.getProcessOutput(
- new File(scriptFile).getParent(),
- new String[]{bin_path, scriptFile},
- add_environment, MINUTE
- );
+ final ProcessOutput run_result = PySdkUtil.getProcessOutput(new File(scriptFile).getParent(), new String[]{binaryPath, scriptFile},
+ getVirtualEnvExtraEnv(binaryPath), MINUTE);
if (!run_result.checkSuccess(LOG)) {
throw new InvalidSdkException(String.format("Failed to determine Python's sys.path value:\nSTDOUT: %s\nSTDERR: %s",
run_result.getStdout(),
@@ -789,15 +786,16 @@ public class PythonSdkType extends SdkType {
return run_result.getStdoutLines();
}
- // Returns a piece of env good as additional env for getProcessOutput.
+ /**
+ * Returns a piece of env good as additional env for getProcessOutput.
+ */
@Nullable
- public static String[] getVirtualEnvAdditionalEnv(String bin_path) {
- File virtualenv_root = getVirtualEnvRoot(bin_path);
- String[] add_environment = null;
- if (virtualenv_root != null) {
- add_environment = new String[]{"PATH=" + virtualenv_root + File.pathSeparator};
+ public static Map<String, String> getVirtualEnvExtraEnv(@NotNull String binaryPath) {
+ final File root = getVirtualEnvRoot(binaryPath);
+ if (root != null) {
+ return ImmutableMap.of("PATH", root.toString());
}
- return add_environment;
+ return null;
}
@Nullable
diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java
index b4b968fdb108..fb77cdb26f8f 100644
--- a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java
+++ b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java
@@ -32,7 +32,6 @@ import com.intellij.openapi.projectRoots.SdkModificator;
import com.intellij.openapi.projectRoots.SdkTypeId;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.startup.StartupActivity;
-import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -149,7 +148,7 @@ public class PythonSdkUpdater implements StartupActivity {
}
public static void updateSdk(@Nullable Project project, @Nullable Component ownerComponent, @NotNull final Sdk sdk, String skeletonsPath) throws InvalidSdkException {
- PySkeletonRefresher.refreshSkeletonsOfSdk(project, ownerComponent, skeletonsPath, new Ref<Boolean>(false), sdk); // NOTE: whole thing would need a rename
+ PySkeletonRefresher.refreshSkeletonsOfSdk(project, ownerComponent, skeletonsPath, sdk); // NOTE: whole thing would need a rename
if (!PySdkUtil.isRemote(sdk)) {
updateSysPath(sdk);
}
diff --git a/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java b/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java
index 9d22af8c03bf..3a9ac951509d 100644
--- a/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java
+++ b/python/src/com/jetbrains/python/sdk/flavors/WinPythonSdkFlavor.java
@@ -37,6 +37,8 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor {
"HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Python\\PythonCore", "python.exe",
"HKEY_LOCAL_MACHINE\\SOFTWARE\\IronPython", "ipy.exe");
+ private static Set<String> ourRegistryCache;
+
private WinPythonSdkFlavor() {
}
@@ -78,18 +80,25 @@ public class WinPythonSdkFlavor extends CPythonSdkFlavor {
}
public static void findInRegistry(Collection<String> candidates) {
- for (Map.Entry<String, String> entry : ourRegistryMap.entrySet()) {
- final String prefix = entry.getKey();
- final String exePath = entry.getValue();
- List<String> strings = WindowsRegistryUtil.readRegistryBranch(prefix);
- for (String string : strings) {
- final String path =
- WindowsRegistryUtil.readRegistryDefault(prefix + "\\" + string +
- "\\InstallPath");
- if (path != null) {
- File f = new File(path, exePath);
- if (f.exists()) {
- candidates.add(FileUtil.toSystemDependentName(f.getPath()));
+ fillRegistryCache();
+ candidates.addAll(ourRegistryCache);
+ }
+
+ private static void fillRegistryCache() {
+ if (ourRegistryCache == null) {
+ ourRegistryCache = new HashSet<String>();
+ for (Map.Entry<String, String> entry : ourRegistryMap.entrySet()) {
+ final String prefix = entry.getKey();
+ final String exePath = entry.getValue();
+ List<String> strings = WindowsRegistryUtil.readRegistryBranch(prefix);
+ for (String string : strings) {
+ final String path = WindowsRegistryUtil.readRegistryDefault(prefix + "\\" + string +
+ "\\InstallPath");
+ if (path != null) {
+ File f = new File(path, exePath);
+ if (f.exists()) {
+ ourRegistryCache.add(FileUtil.toSystemDependentName(f.getPath()));
+ }
}
}
}
diff --git a/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonGenerator.java b/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonGenerator.java
index 901472e6d2c3..7e099041e16a 100644
--- a/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonGenerator.java
+++ b/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonGenerator.java
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.sdk.skeletons;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
@@ -52,21 +53,12 @@ public class PySkeletonGenerator {
ENV_PATH_PARAM.put(IronPythonSdkFlavor.class, "IRONPYTHONPATH"); // TODO: Make strategy and move to PythonSdkFlavor?
}
-
protected static final Logger LOG = Logger.getInstance("#" + PySkeletonGenerator.class.getName());
-
-
protected static final int MINUTE = 60 * 1000;
-
protected static final String GENERATOR3 = "generator3.py";
- private static final String[] EMPTY_ENVS = new String[0];
private final String mySkeletonsPath;
- /**
- * Env variables to be added to skeleton generator
- */
- @NotNull
- private final String[] myEnvs;
+ @NotNull private final Map<String, String> myEnv;
public void finishSkeletonsGeneration() {
}
@@ -85,7 +77,6 @@ public class PySkeletonGenerator {
}
}
-
/**
* @param skeletonPath path where skeletons should be generated
* @param pySdk SDK
@@ -94,11 +85,11 @@ public class PySkeletonGenerator {
public PySkeletonGenerator(String skeletonPath, @NotNull final Sdk pySdk, @Nullable final String currentFolder) {
mySkeletonsPath = skeletonPath;
final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(pySdk);
- if ((currentFolder != null) && (flavor != null) && ENV_PATH_PARAM.containsKey(flavor.getClass())) {
- myEnvs = new String[]{String.format("%s=%s", ENV_PATH_PARAM.get(flavor.getClass()), currentFolder)};
+ if (currentFolder != null && flavor != null && ENV_PATH_PARAM.containsKey(flavor.getClass())) {
+ myEnv = ImmutableMap.of(ENV_PATH_PARAM.get(flavor.getClass()), currentFolder);
}
else {
- myEnvs = EMPTY_ENVS;
+ myEnv = Collections.emptyMap();
}
}
@@ -171,23 +162,19 @@ public class PySkeletonGenerator {
if (modfilename != null) {
commandLine.add(modfilename);
}
- final List<String> envs = new ArrayList<String>(Arrays.asList(myEnvs));
- final String[] virtualEnvAdditionalEnv = PythonSdkType.getVirtualEnvAdditionalEnv(binaryPath);
- if (virtualEnvAdditionalEnv != null) {
- envs.addAll(Arrays.asList(virtualEnvAdditionalEnv));
- }
+ final Map<String, String> extraEnv = PythonSdkType.getVirtualEnvExtraEnv(binaryPath);
+ final Map<String, String> env = extraEnv != null ? PySdkUtil.mergeEnvVariables(myEnv, extraEnv) : myEnv;
- return getProcessOutput(parent_dir, ArrayUtil.toStringArray(commandLine), envs.toArray(new String[envs.size()]),
- MINUTE * 10
- );
+ return getProcessOutput(parent_dir, ArrayUtil.toStringArray(commandLine), env, MINUTE * 10);
}
- protected ProcessOutput getProcessOutput(String homePath, String[] commandLine, String[] env, int timeout) throws InvalidSdkException {
+ protected ProcessOutput getProcessOutput(String homePath, String[] commandLine, Map<String, String> extraEnv,
+ int timeout) throws InvalidSdkException {
return PySdkUtil.getProcessOutput(
homePath,
commandLine,
- env,
+ extraEnv,
timeout
);
}
@@ -207,7 +194,7 @@ public class PySkeletonGenerator {
"-d", mySkeletonsPath, // output dir
"-b", // for builtins
},
- PythonSdkType.getVirtualEnvAdditionalEnv(binaryPath), MINUTE * 5
+ PythonSdkType.getVirtualEnvExtraEnv(binaryPath), MINUTE * 5
);
runResult.checkSuccess(LOG);
LOG.info("Rebuilding builtin skeletons took " + (System.currentTimeMillis() - startTime) + " ms");
@@ -228,7 +215,7 @@ public class PySkeletonGenerator {
final ProcessOutput process = getProcessOutput(parentDir,
ArrayUtil.toStringArray(cmd),
- PythonSdkType.getVirtualEnvAdditionalEnv(homePath),
+ PythonSdkType.getVirtualEnvExtraEnv(homePath),
MINUTE * 4); // see PY-3898
LOG.info("Retrieving binary module list took " + (System.currentTimeMillis() - startTime) + " ms");
diff --git a/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java b/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java
index 4e4a43102202..b1e8ca19acf8 100644
--- a/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java
+++ b/python/src/com/jetbrains/python/sdk/skeletons/PySkeletonRefresher.java
@@ -19,9 +19,6 @@ import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.execution.ExecutionException;
-import com.intellij.notification.Notification;
-import com.intellij.notification.NotificationType;
-import com.intellij.notification.Notifications;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -31,7 +28,6 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
@@ -48,7 +44,6 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackageManager;
-import com.jetbrains.python.packaging.PyPackageManagerImpl;
import com.jetbrains.python.psi.resolve.PythonSdkPathCache;
import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
import com.jetbrains.python.sdk.InvalidSdkException;
@@ -105,10 +100,6 @@ public class PySkeletonRefresher {
private PySkeletonGenerator mySkeletonsGenerator;
- public static void refreshSkeletonsOfSdk(@NotNull Project project, @NotNull Sdk sdk) throws InvalidSdkException {
- refreshSkeletonsOfSdk(project, null, PythonSdkType.findSkeletonsPath(sdk), new Ref<Boolean>(false), sdk);
- }
-
public static synchronized boolean isGeneratingSkeletons() {
return ourGeneratingCount > 0;
}
@@ -120,7 +111,6 @@ public class PySkeletonRefresher {
public static void refreshSkeletonsOfSdk(@Nullable Project project,
Component ownerComponent,
String skeletonsPath,
- @Nullable Ref<Boolean> migrationFlag,
@NotNull Sdk sdk)
throws InvalidSdkException {
final Map<String, List<String>> errors = new TreeMap<String, List<String>>();
@@ -137,7 +127,7 @@ public class PySkeletonRefresher {
changeGeneratingSkeletons(1);
try {
- List<String> sdkErrors = refresher.regenerateSkeletons(checker, migrationFlag);
+ List<String> sdkErrors = refresher.regenerateSkeletons(checker);
if (sdkErrors.size() > 0) {
String sdkName = sdk.getName();
List<String> knownErrors = errors.get(sdkName);
@@ -286,8 +276,7 @@ public class PySkeletonRefresher {
return mySkeletonsPath;
}
- public List<String> regenerateSkeletons(@Nullable SkeletonVersionChecker cachedChecker,
- @Nullable Ref<Boolean> migrationFlag) throws InvalidSdkException {
+ public List<String> regenerateSkeletons(@Nullable SkeletonVersionChecker cachedChecker) throws InvalidSdkException {
final List<String> errorList = new SmartList<String>();
final String homePath = mySdk.getHomePath();
final String skeletonsPath = getSkeletonsPath();
@@ -299,14 +288,13 @@ public class PySkeletonRefresher {
final String readablePath = FileUtil.getLocationRelativeToUserHome(homePath);
mySkeletonsGenerator.prepare();
-
myBlacklist = loadBlacklist();
indicate(PyBundle.message("sdk.gen.querying.$0", readablePath));
// get generator version and binary libs list in one go
- final PySkeletonGenerator.ListBinariesResult binaries =
- mySkeletonsGenerator.listBinaries(mySdk, calculateExtraSysPath(mySdk, getSkeletonsPath()));
+ final String extraSysPath = calculateExtraSysPath(mySdk, getSkeletonsPath());
+ final PySkeletonGenerator.ListBinariesResult binaries = mySkeletonsGenerator.listBinaries(mySdk, extraSysPath);
myGeneratorVersion = binaries.generatorVersion;
myPregeneratedSkeletons = findPregeneratedSkeletons();
@@ -325,77 +313,19 @@ public class PySkeletonRefresher {
final SkeletonHeader oldHeader = readSkeletonHeader(builtinsFile);
final boolean oldOrNonExisting = oldHeader == null || oldHeader.getVersion() == 0;
- if (migrationFlag != null && !migrationFlag.get() && oldOrNonExisting) {
- migrationFlag.set(true);
- Notifications.Bus.notify(
- new Notification(
- PythonSdkType.SKELETONS_TOPIC, PyBundle.message("sdk.gen.notify.converting.old.skels"),
- PyBundle.message("sdk.gen.notify.converting.text"),
- NotificationType.INFORMATION
- )
- );
- }
-
if (myPregeneratedSkeletons != null && oldOrNonExisting) {
- indicate("Unpacking pregenerated skeletons...");
- try {
- final VirtualFile jar = JarFileSystem.getInstance().getVirtualFileForJar(myPregeneratedSkeletons);
- if (jar != null) {
- ZipUtil.extract(new File(jar.getPath()),
- new File(getSkeletonsPath()), null);
- }
- }
- catch (IOException e) {
- LOG.info("Error unpacking pregenerated skeletons", e);
- }
+ unpackPreGeneratedSkeletons();
}
if (oldOrNonExisting) {
- final Sdk base = PythonSdkType.getInstance().getVirtualEnvBaseSdk(mySdk);
- if (base != null) {
- indicate("Copying base SDK skeletons for virtualenv...");
- final String baseSkeletonsPath = PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), base.getHomePath());
- final PySkeletonGenerator.ListBinariesResult baseBinaries =
- mySkeletonsGenerator.listBinaries(base, calculateExtraSysPath(base, baseSkeletonsPath));
- for (Map.Entry<String, PyBinaryItem> entry : binaries.modules.entrySet()) {
- final String module = entry.getKey();
- final PyBinaryItem binary = entry.getValue();
- final PyBinaryItem baseBinary = baseBinaries.modules.get(module);
- final File fromFile = getSkeleton(module, baseSkeletonsPath);
- if (baseBinaries.modules.containsKey(module) &&
- fromFile.exists() &&
- binary.length() == baseBinary.length()) { // Weak binary modules equality check
- final File toFile = fromFile.isDirectory() ?
- getPackageSkeleton(module, skeletonsPath) :
- getModuleSkeleton(module, skeletonsPath);
- try {
- FileUtil.copy(fromFile, toFile);
- }
- catch (IOException e) {
- LOG.info("Error copying base virtualenv SDK skeleton for " + module, e);
- }
- }
- }
- }
+ copyBaseSdkSkeletonsToVirtualEnv(skeletonsPath, binaries);
}
- final SkeletonHeader newHeader = readSkeletonHeader(builtinsFile);
- final boolean mustUpdateBuiltins = myPregeneratedSkeletons == null &&
- (newHeader == null || newHeader.getVersion() < myVersionChecker.getBuiltinVersion());
- if (mustUpdateBuiltins) {
- indicate(PyBundle.message("sdk.gen.updating.builtins.$0", readablePath));
- mySkeletonsGenerator.generateBuiltinSkeletons(mySdk);
- if (myProject != null) {
- PythonSdkPathCache.getInstance(myProject, mySdk).clearBuiltins();
- }
- }
+ final boolean builtinsUpdated = updateSkeletonsForBuiltins(readablePath, builtinsFile);
if (!binaries.modules.isEmpty()) {
-
indicate(PyBundle.message("sdk.gen.updating.$0", readablePath));
-
- List<UpdateResult> updateErrors = updateOrCreateSkeletons(binaries.modules); //Skeletons regeneration
-
+ final List<UpdateResult> updateErrors = updateOrCreateSkeletons(binaries.modules);
if (updateErrors.size() > 0) {
indicateMinor(BLACKLIST_FILE_NAME);
for (UpdateResult error : updateErrors) {
@@ -410,7 +340,6 @@ public class PySkeletonRefresher {
}
indicate(PyBundle.message("sdk.gen.reloading"));
-
mySkeletonsGenerator.refreshGeneratedSkeletons();
if (!oldOrNonExisting) {
@@ -419,14 +348,15 @@ public class PySkeletonRefresher {
}
if (PySdkUtil.isRemote(mySdk)) {
try {
- ((PyPackageManagerImpl)PyPackageManager.getInstance(mySdk)).loadPackages();
+ // Force loading packages
+ PyPackageManager.getInstance(mySdk).getPackages(false);
}
catch (PyExternalProcessException e) {
// ignore - already logged
}
}
- if ((mustUpdateBuiltins || PySdkUtil.isRemote(mySdk)) && myProject != null) {
+ if ((builtinsUpdated || PySdkUtil.isRemote(mySdk)) && myProject != null) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
@@ -438,6 +368,64 @@ public class PySkeletonRefresher {
return errorList;
}
+ private boolean updateSkeletonsForBuiltins(String readablePath, File builtinsFile) throws InvalidSdkException {
+ final SkeletonHeader newHeader = readSkeletonHeader(builtinsFile);
+ final boolean mustUpdateBuiltins = myPregeneratedSkeletons == null &&
+ (newHeader == null || newHeader.getVersion() < myVersionChecker.getBuiltinVersion());
+ if (mustUpdateBuiltins) {
+ indicate(PyBundle.message("sdk.gen.updating.builtins.$0", readablePath));
+ mySkeletonsGenerator.generateBuiltinSkeletons(mySdk);
+ if (myProject != null) {
+ PythonSdkPathCache.getInstance(myProject, mySdk).clearBuiltins();
+ }
+ }
+ return mustUpdateBuiltins;
+ }
+
+ private void copyBaseSdkSkeletonsToVirtualEnv(String skeletonsPath, PySkeletonGenerator.ListBinariesResult binaries)
+ throws InvalidSdkException {
+ final Sdk base = PythonSdkType.getInstance().getVirtualEnvBaseSdk(mySdk);
+ if (base != null) {
+ indicate("Copying base SDK skeletons for virtualenv...");
+ final String baseSkeletonsPath = PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), base.getHomePath());
+ final PySkeletonGenerator.ListBinariesResult baseBinaries =
+ mySkeletonsGenerator.listBinaries(base, calculateExtraSysPath(base, baseSkeletonsPath));
+ for (Map.Entry<String, PyBinaryItem> entry : binaries.modules.entrySet()) {
+ final String module = entry.getKey();
+ final PyBinaryItem binary = entry.getValue();
+ final PyBinaryItem baseBinary = baseBinaries.modules.get(module);
+ final File fromFile = getSkeleton(module, baseSkeletonsPath);
+ if (baseBinaries.modules.containsKey(module) &&
+ fromFile.exists() &&
+ binary.length() == baseBinary.length()) { // Weak binary modules equality check
+ final File toFile = fromFile.isDirectory() ?
+ getPackageSkeleton(module, skeletonsPath) :
+ getModuleSkeleton(module, skeletonsPath);
+ try {
+ FileUtil.copy(fromFile, toFile);
+ }
+ catch (IOException e) {
+ LOG.info("Error copying base virtualenv SDK skeleton for " + module, e);
+ }
+ }
+ }
+ }
+ }
+
+ private void unpackPreGeneratedSkeletons() throws InvalidSdkException {
+ indicate("Unpacking pregenerated skeletons...");
+ try {
+ final VirtualFile jar = JarFileSystem.getInstance().getVirtualFileForJar(myPregeneratedSkeletons);
+ if (jar != null) {
+ ZipUtil.extract(new File(jar.getPath()),
+ new File(getSkeletonsPath()), null);
+ }
+ }
+ catch (IOException e) {
+ LOG.info("Error unpacking pregenerated skeletons", e);
+ }
+ }
+
@Nullable
public static SkeletonHeader readSkeletonHeader(@NotNull File file) {
try {
@@ -835,7 +823,7 @@ public class PySkeletonRefresher {
return null;
}
LOG.info("Pregenerated skeletons root is " + root);
- final String versionString = mySdk.getVersionString();
+ @NonNls final String versionString = mySdk.getVersionString();
if (versionString == null) {
return null;
}
diff --git a/python/src/com/jetbrains/python/statistics/PyPackageUsagesCollector.java b/python/src/com/jetbrains/python/statistics/PyPackageUsagesCollector.java
index f8b4adde5b33..8e029e042aa0 100644
--- a/python/src/com/jetbrains/python/statistics/PyPackageUsagesCollector.java
+++ b/python/src/com/jetbrains/python/statistics/PyPackageUsagesCollector.java
@@ -25,7 +25,7 @@ import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.jetbrains.python.packaging.PyPIPackageUtil;
-import com.jetbrains.python.packaging.PyPackageManagerImpl;
+import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.packaging.PyRequirement;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
@@ -46,12 +46,12 @@ public class PyPackageUsagesCollector extends AbstractApplicationUsagesCollector
public Set<UsageDescriptor> getProjectUsages(@NotNull Project project) throws CollectUsagesException {
final Set<UsageDescriptor> result = new HashSet<UsageDescriptor>();
for(final Module m: ModuleManager.getInstance(project).getModules()) {
- Sdk pythonSdk = PythonSdkType.findPythonSdk(m);
+ final Sdk pythonSdk = PythonSdkType.findPythonSdk(m);
if (pythonSdk != null) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
- List<PyRequirement> requirements = PyPackageManagerImpl.getRequirements(m);
+ List<PyRequirement> requirements = PyPackageManager.getInstance(pythonSdk).getRequirements(m);
if (requirements != null) {
Collection<String> packages = new HashSet<String>(PyPIPackageUtil.INSTANCE.getPackageNames());
for (PyRequirement requirement : requirements) {
diff --git a/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java b/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java
index 93c44f7f2a17..de9f7bf5a5ca 100644
--- a/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java
+++ b/python/src/com/jetbrains/python/structureView/PyStructureViewElement.java
@@ -23,7 +23,6 @@ import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.LayeredIcon;
-import com.intellij.util.Function;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import icons.PythonIcons;
@@ -33,9 +32,6 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
-import static com.intellij.openapi.util.text.StringUtil.join;
-import static com.intellij.openapi.util.text.StringUtil.notNullize;
-
/**
* Handles nodes in Structure View.
* @author yole
@@ -237,32 +233,15 @@ public class PyStructureViewElement implements StructureViewTreeElement {
@NotNull
@Override
public ItemPresentation getPresentation() {
+ final ItemPresentation presentation = myElement.getPresentation();
return new ColoredItemPresentation() {
+ @Nullable
+ @Override
public String getPresentableText() {
- final String unnamed = "<unnamed>";
- if (myElement instanceof PyFunction) {
- PyParameterList argList = ((PyFunction) myElement).getParameterList();
- StringBuilder result = new StringBuilder(notNullize(myElement.getName(), unnamed));
- result.append(argList.getPresentableText(true));
- return result.toString();
- }
- else if (myElement instanceof PyClass && myElement.isValid()) {
- PyClass c = (PyClass) myElement;
- StringBuilder result = new StringBuilder(notNullize(c.getName(), unnamed));
- PyExpression[] superClassExpressions = c.getSuperClassExpressions();
- if (superClassExpressions.length > 0) {
- result.append("(");
- result.append(join(Arrays.asList(superClassExpressions), new Function<PyExpression, String>() {
- public String fun(PyExpression expr) {
- String name = expr.getText();
- return notNullize(name, unnamed);
- }
- }, ", "));
- result.append(")");
- }
- return result.toString();
+ if (myElement instanceof PyFile) {
+ return myElement.getName();
}
- return notNullize(myElement.getName(), unnamed);
+ return presentation != null ? presentation.getPresentableText() : PyNames.UNNAMED_ELEMENT;
}
@Nullable
diff --git a/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java b/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java
index 0b922514e52e..01c8ac68540c 100644
--- a/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java
+++ b/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java
@@ -28,33 +28,29 @@ import com.intellij.execution.testframework.actions.AbstractRerunFailedTestsActi
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComponentContainer;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.search.GlobalSearchScope;
import com.jetbrains.python.run.AbstractPythonRunConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
-/*
- * User: ktisha
- */
public class PyRerunFailedTestsAction extends AbstractRerunFailedTestsAction {
-
protected PyRerunFailedTestsAction(@NotNull ComponentContainer componentContainer) {
super(componentContainer);
}
@Override
@Nullable
- public MyRunProfile getRunProfile() {
+ protected MyRunProfile getRunProfile(@NotNull ExecutionEnvironment environment) {
final TestFrameworkRunningModel model = getModel();
- if (model == null) return null;
- final AbstractPythonRunConfiguration configuration = (AbstractPythonRunConfiguration)model.getProperties().getConfiguration();
- return new MyTestRunProfile(configuration);
+ if (model == null) {
+ return null;
+ }
+ return new MyTestRunProfile((AbstractPythonRunConfiguration)model.getProperties().getConfiguration());
}
-
private class MyTestRunProfile extends MyRunProfile {
public MyTestRunProfile(RunConfigurationBase configuration) {
@@ -71,6 +67,19 @@ public class PyRerunFailedTestsAction extends AbstractRerunFailedTestsAction {
@Override
public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment env) throws ExecutionException {
final AbstractPythonRunConfiguration configuration = ((AbstractPythonRunConfiguration)getPeer());
+
+ // If configuration wants to take care about rerun itself
+ if (configuration instanceof TestRunConfigurationReRunResponsible) {
+ // TODO: Extract method
+ final Set<PsiElement> failedTestElements = new HashSet<PsiElement>();
+ for (final AbstractTestProxy proxy : getFailedTests(getProject())) {
+ final Location<?> location = proxy.getLocation(getProject(), GlobalSearchScope.allScope(getProject()));
+ if (location != null) {
+ failedTestElements.add(location.getPsiElement());
+ }
+ }
+ return ((TestRunConfigurationReRunResponsible)configuration).rerunTests(executor, env, failedTestElements);
+ }
return new FailedPythonTestCommandLineStateBase(configuration, env,
(PythonTestCommandLineStateBase)configuration.getState(executor, env));
}
diff --git a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java
index 1a5aafe59051..c73a488dc7f5 100644
--- a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java
+++ b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java
@@ -63,6 +63,7 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt
myConfiguration = configuration;
}
+ @Override
@NotNull
protected ConsoleView createAndAttachConsole(Project project, ProcessHandler processHandler, Executor executor)
throws ExecutionException {
@@ -89,6 +90,7 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt
return new PythonTRunnerConsoleProperties(myConfiguration, executor, false);
}
+ @Override
public GeneralCommandLine generateCommandLine() throws ExecutionException {
GeneralCommandLine cmd = super.generateCommandLine();
@@ -135,7 +137,7 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt
PyRerunFailedTestsAction rerunFailedTestsAction = new PyRerunFailedTestsAction(console);
if (console instanceof SMTRunnerConsoleView) {
- rerunFailedTestsAction.init(((BaseTestsOutputConsoleView)console).getProperties(), getEnvironment());
+ rerunFailedTestsAction.init(((BaseTestsOutputConsoleView)console).getProperties());
rerunFailedTestsAction.setModelProvider(new Getter<TestFrameworkRunningModel>() {
@Override
public TestFrameworkRunningModel get() {
diff --git a/python/src/com/jetbrains/python/testing/TestRunConfigurationReRunResponsible.java b/python/src/com/jetbrains/python/testing/TestRunConfigurationReRunResponsible.java
new file mode 100644
index 000000000000..a8e9e1f8d5e4
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/TestRunConfigurationReRunResponsible.java
@@ -0,0 +1,30 @@
+package com.jetbrains.python.testing;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.psi.PsiElement;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * Configuration that handles rerun failed tests itself.
+ *
+ * @author Ilya.Kazakevich
+ */
+public interface TestRunConfigurationReRunResponsible {
+ /**
+ * Rerun failed tests
+ * @param executor test executor
+ * @param environment test environment
+ * @param failedTests a pack of psi elements, indicating failed tests (to retrn)
+ * @return state to run or null if no rerun actions found (i.e. no errors in failedTest, empty etc)
+ * @throws ExecutionException failed to run
+ */
+ @Nullable
+ RunProfileState rerunTests(@NotNull final Executor executor, @NotNull final ExecutionEnvironment environment,
+ @NotNull Collection<PsiElement> failedTests) throws ExecutionException;
+}
diff --git a/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java b/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java
index 9d763e2ce286..64adf2093028 100644
--- a/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java
+++ b/python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java
@@ -35,7 +35,6 @@ import com.intellij.util.ui.update.Update;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackageManager;
-import com.jetbrains.python.packaging.PyPackageManagerImpl;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
@@ -131,9 +130,9 @@ public class VFSTestFrameworkListener implements ApplicationComponent {
LOG.info("Searching test runner in empty sdk");
return null;
}
- final PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk);
+ final PyPackageManager packageManager = PyPackageManager.getInstance(sdk);
try {
- return packageManager.findInstalledPackage(testPackageName) != null;
+ return packageManager.findPackage(testPackageName, false) != null;
}
catch (PyExternalProcessException e) {
LOG.info("Can't load package list " + e.getMessage());
diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java b/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java
index f988dbd315e6..8dc27d4fd995 100644
--- a/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java
+++ b/python/src/com/jetbrains/python/testing/pytest/PyTestConfigurationProducer.java
@@ -31,7 +31,6 @@ import com.intellij.webcore.packaging.PackageVersionComparator;
import com.jetbrains.python.packaging.PyExternalProcessException;
import com.jetbrains.python.packaging.PyPackage;
import com.jetbrains.python.packaging.PyPackageManager;
-import com.jetbrains.python.packaging.PyPackageManagerImpl;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyFunction;
@@ -97,9 +96,9 @@ public class PyTestConfigurationProducer extends PythonTestConfigurationProducer
if (pyFunction != null) {
keywords = pyFunction.getName();
if (pyClass != null) {
- final PyPackageManagerImpl packageManager = (PyPackageManagerImpl)PyPackageManager.getInstance(sdk);
+ final PyPackageManager packageManager = PyPackageManager.getInstance(sdk);
try {
- final PyPackage pytestPackage = packageManager.findInstalledPackage("pytest");
+ final PyPackage pytestPackage = packageManager.findPackage("pytest", false);
if (pytestPackage != null && PackageVersionComparator.VERSION_COMPARATOR.compare(pytestPackage.getVersion(), "2.3.3") >= 0) {
keywords = pyClass.getName() + " and " + keywords;
}
diff --git a/python/src/com/jetbrains/python/validation/Pep8ExternalAnnotator.java b/python/src/com/jetbrains/python/validation/Pep8ExternalAnnotator.java
index a7faa6ebb98f..22bb1d308d2c 100644
--- a/python/src/com/jetbrains/python/validation/Pep8ExternalAnnotator.java
+++ b/python/src/com/jetbrains/python/validation/Pep8ExternalAnnotator.java
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.validation;
+import com.google.common.collect.ImmutableMap;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInsight.intention.IntentionAction;
@@ -173,7 +174,7 @@ public class Pep8ExternalAnnotator extends ExternalAnnotator<Pep8ExternalAnnotat
options.add("-");
ProcessOutput output = PySdkUtil.getProcessOutput(new File(collectedInfo.interpreterPath).getParent(),
ArrayUtil.toStringArray(options),
- new String[] { "PYTHONUNBUFFERED=1" },
+ ImmutableMap.of("PYTHONBUFFERED", "1"),
10000,
collectedInfo.fileText.getBytes(), false);
diff --git a/python/src/com/jetbrains/python/validation/StringLiteralQuotesAnnotator.java b/python/src/com/jetbrains/python/validation/StringLiteralQuotesAnnotator.java
index 0666e8319caa..997123351d36 100644
--- a/python/src/com/jetbrains/python/validation/StringLiteralQuotesAnnotator.java
+++ b/python/src/com/jetbrains/python/validation/StringLiteralQuotesAnnotator.java
@@ -18,8 +18,10 @@ package com.jetbrains.python.validation;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
+import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyStringLiteralExpression;
import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl;
+import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -29,17 +31,16 @@ import java.util.List;
* @author dcheryasov
*/
public class StringLiteralQuotesAnnotator extends PyAnnotator {
- public static final String MISSING_Q = "Missing closing quote";
private static final String TRIPLE_QUOTES = "\"\"\"";
private static final String TRIPLE_APOS = "'''";
public void visitPyStringLiteralExpression(final PyStringLiteralExpression node) {
- List<ASTNode> stringNodes = node.getStringNodes();
+ final List<ASTNode> stringNodes = node.getStringNodes();
for (ASTNode stringNode : stringNodes) {
- boolean foundError;
- String nodeText = stringNode.getText();
- int index = PyStringLiteralExpressionImpl.getPrefixLength(nodeText);
- String unprefixed = nodeText.substring(index);
+ final String nodeText = stringNode.getText();
+ final int index = PyStringLiteralExpressionImpl.getPrefixLength(nodeText);
+ final String unprefixed = nodeText.substring(index);
+ final boolean foundError;
if (StringUtil.startsWith(unprefixed, TRIPLE_QUOTES)) {
foundError = checkTripleQuotedString(stringNode, unprefixed, TRIPLE_QUOTES);
}
@@ -49,23 +50,33 @@ public class StringLiteralQuotesAnnotator extends PyAnnotator {
else {
foundError = checkQuotedString(stringNode, unprefixed);
}
- if (foundError) break;
+ if (foundError) {
+ break;
+ }
}
}
- private boolean checkQuotedString(ASTNode stringNode, String nodeText) {
- char firstQuote = nodeText.charAt(0);
- int lastChar = nodeText.length()-1;
- if (lastChar == 0 || nodeText.charAt(lastChar) != firstQuote ||
- (nodeText.charAt(lastChar-1) == '\\' && (lastChar == 1 || nodeText.charAt(lastChar-2) != '\\'))) {
- getHolder().createErrorAnnotation(stringNode, MISSING_Q + " [" + firstQuote + "]");
+ private boolean checkQuotedString(@NotNull ASTNode stringNode, @NotNull String nodeText) {
+ final char firstQuote = nodeText.charAt(0);
+ final char lastChar = nodeText.charAt(nodeText.length() - 1);
+ int precedingBackslashCount = 0;
+ for (int i = nodeText.length() - 2; i >= 0; i--) {
+ if (nodeText.charAt(i) == '\\') {
+ precedingBackslashCount++;
+ }
+ else {
+ break;
+ }
+ }
+ if (nodeText.length() == 1 || lastChar != firstQuote || precedingBackslashCount % 2 != 0) {
+ getHolder().createErrorAnnotation(stringNode, PyBundle.message("ANN.missing.closing.quote", firstQuote));
return true;
}
return false;
}
- private boolean checkTripleQuotedString(ASTNode stringNode, String text, final String quotes) {
- if (text.length() < 6 || !text.endsWith(quotes)) {
+ private boolean checkTripleQuotedString(@NotNull ASTNode stringNode, @NotNull String text, @NotNull String quotes) {
+ if (text.length() < 6 || !text.endsWith(quotes)) {
int startOffset = StringUtil.trimTrailing(stringNode.getText()).lastIndexOf('\n');
if (startOffset < 0) {
startOffset = stringNode.getTextRange().getStartOffset();
@@ -73,8 +84,8 @@ public class StringLiteralQuotesAnnotator extends PyAnnotator {
else {
startOffset = stringNode.getTextRange().getStartOffset() + startOffset + 1;
}
- TextRange highlightRange = new TextRange(startOffset, stringNode.getTextRange().getEndOffset());
- getHolder().createErrorAnnotation(highlightRange, "Missing closing triple quotes");
+ final TextRange highlightRange = new TextRange(startOffset, stringNode.getTextRange().getEndOffset());
+ getHolder().createErrorAnnotation(highlightRange, PyBundle.message("ANN.missing.closing.triple.quotes"));
return true;
}
return false;
diff --git a/python/testData/codeInsight/smartEnter/withOnlyColonMissing.py b/python/testData/codeInsight/smartEnter/withOnlyColonMissing.py
new file mode 100644
index 000000000000..7fc16ea3bb68
--- /dev/null
+++ b/python/testData/codeInsight/smartEnter/withOnlyColonMissing.py
@@ -0,0 +1 @@
+with open('file.txt') as f<caret> \ No newline at end of file
diff --git a/python/testData/codeInsight/smartEnter/withOnlyColonMissing_after.py b/python/testData/codeInsight/smartEnter/withOnlyColonMissing_after.py
new file mode 100644
index 000000000000..09a090ef65d3
--- /dev/null
+++ b/python/testData/codeInsight/smartEnter/withOnlyColonMissing_after.py
@@ -0,0 +1,2 @@
+with open('file.txt') as f:
+ <caret> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml
new file mode 100644
index 000000000000..28d3684cdb10
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_callee_verification.xml
@@ -0,0 +1 @@
+<node text="target_func() (hierarchy.call.Static.ArgumentList.file_1)" base="true"/> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml
new file mode 100644
index 000000000000..28d3684cdb10
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/ArgumentList/ArgumentList_caller_verification.xml
@@ -0,0 +1 @@
+<node text="target_func() (hierarchy.call.Static.ArgumentList.file_1)" base="true"/> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/ArgumentList/file_1.py b/python/testData/hierarchy/call/Static/ArgumentList/file_1.py
new file mode 100644
index 000000000000..ea497204d2ca
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/ArgumentList/file_1.py
@@ -0,0 +1,8 @@
+def target_func():
+ pass
+
+def func1(f):
+ f()
+
+def func2(f):
+ return f \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/ArgumentList/main.py b/python/testData/hierarchy/call/Static/ArgumentList/main.py
new file mode 100644
index 000000000000..b479ea6f9d3a
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/ArgumentList/main.py
@@ -0,0 +1,8 @@
+from file_1 import *
+
+target_<caret>func()
+func1(target_func)
+func1(target_func())
+func2(target_func)
+func2(target_func())
+
diff --git a/python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml b/python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml
new file mode 100644
index 000000000000..1b720a99aee9
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Constructor/Constructor_callee_verification.xml
@@ -0,0 +1,8 @@
+<node text="A.__init__(self) (hierarchy.call.Static.Constructor.main)" base="true">
+ <node text="invoke1(p) (hierarchy.call.Static.Constructor.main)">
+ <node text="A.method1(self) (hierarchy.call.Static.Constructor.main)"/>
+ </node>
+ <node text="invoke2(p) (hierarchy.call.Static.Constructor.main)">
+ <node text="A.method2(self) (hierarchy.call.Static.Constructor.main)"/>
+ </node>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml b/python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml
new file mode 100644
index 000000000000..b872e20e6c7b
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Constructor/Constructor_caller_verification.xml
@@ -0,0 +1,6 @@
+<node text="A.__init__(self) (hierarchy.call.Static.Constructor.main)" base="true">
+ <node text="invokeA() (hierarchy.call.Static.Constructor.main)">
+ <node text="C.bar(self) (hierarchy.call.Static.Constructor.main)"/>
+ </node>
+ <node text="C.bar(self) (hierarchy.call.Static.Constructor.main)"/>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Constructor/main.py b/python/testData/hierarchy/call/Static/Constructor/main.py
new file mode 100644
index 000000000000..1c2e14776c25
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Constructor/main.py
@@ -0,0 +1,32 @@
+class A():
+ def __init__(self):
+ invoke1(self)
+ invoke2(self)
+
+ def method1(self):
+ pass
+
+ def method2(self):
+ pass
+
+def invoke1(p):
+ p.method1()
+
+
+def invoke2(p):
+ p.method2()
+
+
+def invokeA():
+ a = A()
+ a.method1()
+ a.method2()
+
+ def new_class_func():
+ class C():
+ def bar(self):
+ invokeA(A())
+ return C()
+
+a = A()
+A.__init_<caret>_(a) \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml
new file mode 100644
index 000000000000..23fc966f8913
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_callee_verification.xml
@@ -0,0 +1 @@
+<node text="target_func() (hierarchy.call.Static.DefaultValue.main)" base="true"/> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml
new file mode 100644
index 000000000000..2cb93be11635
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/DefaultValue/DefaultValue_caller_verification.xml
@@ -0,0 +1,4 @@
+<node text="target_func() (hierarchy.call.Static.DefaultValue.main)" base="true">
+ <node text="func2() (hierarchy.call.Static.DefaultValue.main)"/>
+ <node text="func4() (hierarchy.call.Static.DefaultValue.main)"/>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/DefaultValue/main.py b/python/testData/hierarchy/call/Static/DefaultValue/main.py
new file mode 100644
index 000000000000..223642493ef4
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/DefaultValue/main.py
@@ -0,0 +1,30 @@
+def target_func():
+ pass
+
+
+def func1():
+ def inner_func1(x=target_func):
+ pass
+
+ return inner_func1(target_func)
+
+
+def func2():
+ def inner_func2(x=target_func()):
+ pass
+
+ return inner_func2(target_func)
+
+
+def func3(x=target_func()):
+ pass
+
+
+def func4():
+ def inner_func4(x=target_func):
+ pass
+
+ return inner_func4(target_func())
+
+
+target_<caret>func() \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml
new file mode 100644
index 000000000000..cf65c63a4d04
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_callee_verification.xml
@@ -0,0 +1 @@
+<node text="A.target_func(self) (hierarchy.call.Static.Inheritance.main)" base="true"/> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml
new file mode 100644
index 000000000000..a81d537dcc27
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Inheritance/Inheritance_caller_verification.xml
@@ -0,0 +1,4 @@
+<node text="A.target_func(self) (hierarchy.call.Static.Inheritance.main)" base="true">
+ <node text="C.func(self, a) (hierarchy.call.Static.Inheritance.main)"/>
+ <node text="foo2(b) (hierarchy.call.Static.Inheritance.main)"/>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Inheritance/main.py b/python/testData/hierarchy/call/Static/Inheritance/main.py
new file mode 100644
index 000000000000..89c37de9bceb
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Inheritance/main.py
@@ -0,0 +1,36 @@
+
+class A(object):
+ def target_func(self):
+ pass
+
+
+class B(A):
+ pass
+
+
+class C(object):
+ def func(self, a):
+ a.target_func()
+
+
+def foo1(b):
+ f = b.target_func
+
+
+def foo2(b):
+ b.target_func()
+
+
+def bar1(*args):
+ pass
+
+
+def bar2(*args):
+ pass
+
+
+b = B()
+foo1(b)
+foo2(b)
+bar1(b.target_<caret>func)
+bar2(b.target_func()) \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml
new file mode 100644
index 000000000000..551a5bd0fdf0
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_callee_verification.xml
@@ -0,0 +1,5 @@
+<node text="target_func() (hierarchy.call.Static.InnerFunction.main)" base="true">
+ <node text="foo() (hierarchy.call.Static.InnerFunction.main)">
+ <node text="bar() (hierarchy.call.Static.InnerFunction.main)"/>
+ </node>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml
new file mode 100644
index 000000000000..f01b7f50e287
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/InnerFunction/InnerFunction_caller_verification.xml
@@ -0,0 +1 @@
+<node text="target_func() (hierarchy.call.Static.InnerFunction.main)" base="true"/> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/InnerFunction/main.py b/python/testData/hierarchy/call/Static/InnerFunction/main.py
new file mode 100644
index 000000000000..7ffc14aaf745
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/InnerFunction/main.py
@@ -0,0 +1,9 @@
+def bar():
+ pass
+
+def target_func():
+ def foo():
+ return bar()
+ foo()
+
+target_<caret>func() \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml b/python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml
new file mode 100644
index 000000000000..c1b05951f7d4
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Lambda/Lambda_callee_verification.xml
@@ -0,0 +1,8 @@
+<node text="target_func(x=func1, y=func2(), z=lambda: func3, w=lambda: func4()) (hierarchy.call.Static.Lambda.main)" base="true">
+ <node text="func8() (hierarchy.call.Static.Lambda.file_1)"/>
+ <node text="func13() (hierarchy.call.Static.Lambda.file_1)"/>
+ <node text="func15() (hierarchy.call.Static.Lambda.file_1)"/>
+ <node text="inner(ix=func7, iy=func8(), iz=lambda: func9, iw=lambda: func10()) (hierarchy.call.Static.Lambda.main)">
+ <node text="func11() (hierarchy.call.Static.Lambda.file_1)"/>
+ </node>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml b/python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml
new file mode 100644
index 000000000000..d54b9674b47d
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Lambda/Lambda_caller_verification.xml
@@ -0,0 +1 @@
+<node text="target_func(x=func1, y=func2(), z=lambda: func3, w=lambda: func4()) (hierarchy.call.Static.Lambda.main)" base="true"/> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Lambda/file_1.py b/python/testData/hierarchy/call/Static/Lambda/file_1.py
new file mode 100644
index 000000000000..bf1185212963
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Lambda/file_1.py
@@ -0,0 +1,18 @@
+def func1(): pass
+def func2(): pass
+def func3(): pass
+def func4(): pass
+def func5(): pass
+def func6(): pass
+def func7(): pass
+def func8(): pass
+def func9(): pass
+def func10(): pass
+def func11(): pass
+def func12(): pass
+def func13(): pass
+def func14(): pass
+def func15(): pass
+def func16(): pass
+def func17(): pass
+def func18(): pass
diff --git a/python/testData/hierarchy/call/Static/Lambda/main.py b/python/testData/hierarchy/call/Static/Lambda/main.py
new file mode 100644
index 000000000000..7dab14e27566
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Lambda/main.py
@@ -0,0 +1,18 @@
+from file_1 import *
+
+
+def target_func(x=func1, y=func2(), z=lambda: func3, w=lambda: func4()):
+ p1 = lambda: func5()
+ p2 = lambda: func6
+ p1(), p2()
+ def inner(ix=func7, iy=func8(), iz=lambda: func9, iw=lambda: func10()):
+ func11()
+ ip = lambda: func12()
+ ip()
+ func13()
+ inner(func14, func15(), lambda: func16, lambda: func17())
+
+ return func18
+
+
+target_<caret>func() \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml
new file mode 100644
index 000000000000..4e1813bf0bd7
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_callee_verification.xml
@@ -0,0 +1,12 @@
+<node text="target_func() (hierarchy.call.Static.NestedCall.main)" base="true">
+ <node text="func5(*args) (hierarchy.call.Static.NestedCall.file_1)"/>
+ <node text="func3(*args) (hierarchy.call.Static.NestedCall.file_1)"/>
+ <node text="func6(*args) (hierarchy.call.Static.NestedCall.file_1)"/>
+ <node text="func7(*args) (hierarchy.call.Static.NestedCall.file_1)"/>
+ <node text="func2(*args) (hierarchy.call.Static.NestedCall.file_1)"/>
+ <node text="func1(*args) (hierarchy.call.Static.NestedCall.file_1)"/>
+ <node text="func10(*args) (hierarchy.call.Static.NestedCall.file_1)"/>
+ <node text="inner(*args) (hierarchy.call.Static.NestedCall.main)">
+ <node text="func1(*args) (hierarchy.call.Static.NestedCall.file_1)"/>
+ </node>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml
new file mode 100644
index 000000000000..ea843209ce23
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/NestedCall/NestedCall_caller_verification.xml
@@ -0,0 +1 @@
+<node text="target_func() (hierarchy.call.Static.NestedCall.main)" base="true"/> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/NestedCall/file_1.py b/python/testData/hierarchy/call/Static/NestedCall/file_1.py
new file mode 100644
index 000000000000..3e89b6b290d0
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/NestedCall/file_1.py
@@ -0,0 +1,10 @@
+def func1(*args): pass
+def func2(*args): pass
+def func3(*args): pass
+def func4(*args): pass
+def func5(*args): pass
+def func6(*args): pass
+def func7(*args): pass
+def func8(*args): pass
+def func9(*args): pass
+def func10(*args): pass \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/NestedCall/main.py b/python/testData/hierarchy/call/Static/NestedCall/main.py
new file mode 100644
index 000000000000..aca993a9d6a1
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/NestedCall/main.py
@@ -0,0 +1,10 @@
+from file_1 import *
+
+def target_func():
+ def inner(*args):
+ return func1()
+
+ return inner(func1(func2(func3(func4, func5()), func6(), (((func7)))()), func8), func9, func10())
+
+
+target_<caret>func() \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml
new file mode 100644
index 000000000000..04af9fdd87ed
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_callee_verification.xml
@@ -0,0 +1,3 @@
+<node text="B.target_func(self, p) (hierarchy.call.Static.OverriddenMethod.main)" base="true">
+ <node text="A.another_func(self) (hierarchy.call.Static.OverriddenMethod.file_1)"/>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml
new file mode 100644
index 000000000000..ddf9369f2766
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/OverriddenMethod/OverriddenMethod_caller_verification.xml
@@ -0,0 +1,5 @@
+<node text="B.target_func(self, p) (hierarchy.call.Static.OverriddenMethod.main)" base="true">
+ <node text="C.func1(self, a) (hierarchy.call.Static.OverriddenMethod.main)"/>
+ <node text="bar1(a) (hierarchy.call.Static.OverriddenMethod.main)"/>
+ <node text="C.func2(self) (hierarchy.call.Static.OverriddenMethod.main)"/>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py b/python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py
new file mode 100644
index 000000000000..e1e4fb90b949
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/OverriddenMethod/file_1.py
@@ -0,0 +1,8 @@
+
+class A(object):
+ def target_func(self, p):
+ pass
+
+ def another_func(self):
+ pass
+
diff --git a/python/testData/hierarchy/call/Static/OverriddenMethod/main.py b/python/testData/hierarchy/call/Static/OverriddenMethod/main.py
new file mode 100644
index 000000000000..288d638e0fea
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/OverriddenMethod/main.py
@@ -0,0 +1,29 @@
+from file_1 import A
+
+
+class B(A):
+ def target_func(self, p):
+ p.another_func()
+
+
+class C(object):
+ def func1(self, a):
+ a.target_func(A())
+
+ def func2(self):
+ a = A()
+ b = B()
+ a.target_func(b)
+
+
+def bar1(a):
+ a.target_func(a)
+
+
+def bar2(a, b):
+ atf, btf = a.target_func, b.target_func
+
+
+bar1(A())
+bar2(A(), B())
+B().target_<caret>func(A()) \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml
new file mode 100644
index 000000000000..662bc7a74da8
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_callee_verification.xml
@@ -0,0 +1,3 @@
+<node text="target_func() (hierarchy.call.Static.Parentheses.file_1)" base="true">
+ <node text="nothing(x) (hierarchy.call.Static.Parentheses.main)"/>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml
new file mode 100644
index 000000000000..36b8e32e9363
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Parentheses/Parentheses_caller_verification.xml
@@ -0,0 +1,3 @@
+<node text="target_func() (hierarchy.call.Static.Parentheses.file_1)" base="true">
+ <node text="foo(x=bar()) (hierarchy.call.Static.Parentheses.file_1)"/>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Parentheses/file_1.py b/python/testData/hierarchy/call/Static/Parentheses/file_1.py
new file mode 100644
index 000000000000..ed3de6593a59
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Parentheses/file_1.py
@@ -0,0 +1,20 @@
+import main
+
+def target_func():
+ main.nothing(None)
+
+
+def nothing(x):
+ pass
+
+
+def foo(x=bar()):
+ ((target_func))()
+
+
+def bar():
+ main.nothing((target_func))
+
+
+def another():
+ ((((target_func), 1))(), 2)() \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Parentheses/main.py b/python/testData/hierarchy/call/Static/Parentheses/main.py
new file mode 100644
index 000000000000..37fe5bf0819d
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Parentheses/main.py
@@ -0,0 +1,7 @@
+from file_1 import target_func
+
+def nothing(x):
+ pass
+
+
+target_<caret>func() \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml b/python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml
new file mode 100644
index 000000000000..c009281e2fa2
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Simple/Simple_callee_verification.xml
@@ -0,0 +1,3 @@
+<node text="target_func() (hierarchy.call.Static.Simple.main)" base="true">
+ <node text="func1() (hierarchy.call.Static.Simple.main)"/>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml b/python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml
new file mode 100644
index 000000000000..a0f06c09b00a
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Simple/Simple_caller_verification.xml
@@ -0,0 +1,3 @@
+<node text="target_func() (hierarchy.call.Static.Simple.main)" base="true">
+ <node text="func2() (hierarchy.call.Static.Simple.main)"/>
+</node> \ No newline at end of file
diff --git a/python/testData/hierarchy/call/Static/Simple/main.py b/python/testData/hierarchy/call/Static/Simple/main.py
new file mode 100644
index 000000000000..9073f3df5d87
--- /dev/null
+++ b/python/testData/hierarchy/call/Static/Simple/main.py
@@ -0,0 +1,10 @@
+def func1():
+ pass
+
+def func2():
+ target_func()
+
+def target_func():
+ func1()
+
+target_<caret>func()
diff --git a/python/testData/highlighting/multipleEscapedBackslashes.py b/python/testData/highlighting/multipleEscapedBackslashes.py
new file mode 100644
index 000000000000..4eb30abdcdb4
--- /dev/null
+++ b/python/testData/highlighting/multipleEscapedBackslashes.py
@@ -0,0 +1,2 @@
+S1 = 'This literal contains even number of backslashes\\\\\\'
+S2 = <error descr="Missing closing quote [']">'This literal contains odd number of backslashes\\\\\\\'</error> \ No newline at end of file
diff --git a/python/testData/inspections/DefaultArgumentEmptyList.py b/python/testData/inspections/DefaultArgumentEmptyList.py
index 55748eb449d5..a351c5134c90 100644
--- a/python/testData/inspections/DefaultArgumentEmptyList.py
+++ b/python/testData/inspections/DefaultArgumentEmptyList.py
@@ -1 +1 @@
-def foo(args=<warning descr="Default argument value is mutable">[<caret>]</warning>):<EOLError descr="Indent expected"></EOLError> \ No newline at end of file
+def bar(args=<warning descr="Default argument value is mutable">[<caret>]</warning>):<EOLError descr="Indent expected"></EOLError> \ No newline at end of file
diff --git a/python/testData/inspections/DefaultArgumentEmptyList_after.py b/python/testData/inspections/DefaultArgumentEmptyList_after.py
index 2de1bb88ef92..ada9baaa7865 100644
--- a/python/testData/inspections/DefaultArgumentEmptyList_after.py
+++ b/python/testData/inspections/DefaultArgumentEmptyList_after.py
@@ -1,3 +1,3 @@
-def foo(args=None):
+def bar(args=None):
if not args:
args = [] \ No newline at end of file
diff --git a/python/testData/mover/outsideFromDict.py b/python/testData/mover/outsideFromDict.py
new file mode 100644
index 000000000000..52f81fd16a8e
--- /dev/null
+++ b/python/testData/mover/outsideFromDict.py
@@ -0,0 +1,5 @@
+a = {
+ 1:1,
+ # te<caret>st
+ 2:2
+}
diff --git a/python/testData/mover/outsideFromDict_afterDown.py b/python/testData/mover/outsideFromDict_afterDown.py
new file mode 100644
index 000000000000..551adfcd7894
--- /dev/null
+++ b/python/testData/mover/outsideFromDict_afterDown.py
@@ -0,0 +1,5 @@
+a = {
+ 1:1,
+ 2:2
+ # te<caret>st
+}
diff --git a/python/testData/mover/outsideFromDict_afterUp.py b/python/testData/mover/outsideFromDict_afterUp.py
new file mode 100644
index 000000000000..36f665bfc69f
--- /dev/null
+++ b/python/testData/mover/outsideFromDict_afterUp.py
@@ -0,0 +1,5 @@
+a = {
+ # te<caret>st
+ 1:1,
+ 2:2
+}
diff --git a/python/testData/mover/sameLevelAsDict.py b/python/testData/mover/sameLevelAsDict.py
new file mode 100644
index 000000000000..fd5b54ec4e82
--- /dev/null
+++ b/python/testData/mover/sameLevelAsDict.py
@@ -0,0 +1,9 @@
+a = {
+ 'c': 99999
+}
+print "Hello, there."
+a = {
+ 'b': 1,
+ 'c': 2
+}
+print <caret>a['c'] \ No newline at end of file
diff --git a/python/testData/mover/sameLevelAsDict_afterDown.py b/python/testData/mover/sameLevelAsDict_afterDown.py
new file mode 100644
index 000000000000..602da928bab7
--- /dev/null
+++ b/python/testData/mover/sameLevelAsDict_afterDown.py
@@ -0,0 +1,9 @@
+a = {
+ 'c': 99999
+}
+print "Hello, there."
+a = {
+ 'b': 1,
+ 'c': 2
+}
+print a['c'] \ No newline at end of file
diff --git a/python/testData/mover/sameLevelAsDict_afterUp.py b/python/testData/mover/sameLevelAsDict_afterUp.py
new file mode 100644
index 000000000000..c4817e7a6474
--- /dev/null
+++ b/python/testData/mover/sameLevelAsDict_afterUp.py
@@ -0,0 +1,9 @@
+a = {
+ 'c': 99999
+}
+print "Hello, there."
+print a['c']
+a = {
+ 'b': 1,
+ 'c': 2
+}
diff --git a/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java b/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java
index 54afb702322b..8dcc2ec69f6f 100644
--- a/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java
+++ b/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java
@@ -43,7 +43,7 @@ public class PyPackagingTest extends PyEnvTestCase {
final Sdk sdk = createTempSdk(sdkHome, SdkCreationType.EMPTY_SDK);
List<PyPackage> packages = null;
try {
- packages = ((PyPackageManagerImpl)PyPackageManager.getInstance(sdk)).getPackages();
+ packages = PyPackageManager.getInstance(sdk).getPackages(false);
}
catch (PyExternalProcessException e) {
final int retcode = e.getRetcode();
@@ -74,13 +74,13 @@ public class PyPackagingTest extends PyEnvTestCase {
}
final File tempDir = FileUtil.createTempDirectory(getTestName(false), null);
final File venvDir = new File(tempDir, "venv");
- final String venvSdkHome = ((PyPackageManagerImpl)PyPackageManagerImpl.getInstance(sdk)).createVirtualEnv(venvDir.toString(),
- false);
+ final String venvSdkHome = PyPackageManager.getInstance(sdk).createVirtualEnv(venvDir.toString(),
+ false);
final Sdk venvSdk = createTempSdk(venvSdkHome, SdkCreationType.EMPTY_SDK);
assertNotNull(venvSdk);
assertTrue(PythonSdkType.isVirtualEnv(venvSdk));
assertInstanceOf(PythonSdkFlavor.getPlatformIndependentFlavor(venvSdk.getHomePath()), VirtualEnvSdkFlavor.class);
- final List<PyPackage> packages = ((PyPackageManagerImpl)PyPackageManagerImpl.getInstance(venvSdk)).getPackages();
+ final List<PyPackage> packages = PyPackageManager.getInstance(venvSdk).getPackages(false);
final PyPackage setuptools = findPackage("setuptools", packages);
assertNotNull(setuptools);
assertEquals("setuptools", setuptools.getName());
@@ -109,15 +109,15 @@ public class PyPackagingTest extends PyEnvTestCase {
try {
final File tempDir = FileUtil.createTempDirectory(getTestName(false), null);
final File venvDir = new File(tempDir, "venv");
- final String venvSdkHome = ((PyPackageManagerImpl)PyPackageManager.getInstance(sdk)).createVirtualEnv(venvDir.getPath(), false);
+ final String venvSdkHome = PyPackageManager.getInstance(sdk).createVirtualEnv(venvDir.getPath(), false);
final Sdk venvSdk = createTempSdk(venvSdkHome, SdkCreationType.EMPTY_SDK);
assertNotNull(venvSdk);
- final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManager.getInstance(venvSdk);
- final List<PyPackage> packages1 = manager.getPackages();
+ final PyPackageManager manager = PyPackageManager.getInstance(venvSdk);
+ final List<PyPackage> packages1 = manager.getPackages(false);
// TODO: Install Markdown from a local file
manager.install(list(PyRequirement.fromString("Markdown<2.2"),
new PyRequirement("httplib2")), Collections.<String>emptyList());
- final List<PyPackage> packages2 = manager.getPackages();
+ final List<PyPackage> packages2 = manager.getPackages(false);
final PyPackage markdown2 = findPackage("Markdown", packages2);
assertNotNull(markdown2);
assertTrue(markdown2.isInstalled());
@@ -126,7 +126,7 @@ public class PyPackagingTest extends PyEnvTestCase {
assertEquals("pip", pip1.getName());
assertEquals(PyPackageManagerImpl.PIP_VERSION, pip1.getVersion());
manager.uninstall(list(pip1));
- final List<PyPackage> packages3 = manager.getPackages();
+ final List<PyPackage> packages3 = manager.getPackages(false);
final PyPackage pip2 = findPackage("pip", packages3);
assertNull(pip2);
}
diff --git a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java
index 4372715371eb..e29ab4cfdc8b 100644
--- a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java
+++ b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java
@@ -13,7 +13,7 @@ import com.jetbrains.python.console.pydev.PydevCompletionVariant;
import com.jetbrains.python.debugger.PyDebuggerException;
import com.jetbrains.python.debugger.PyExceptionBreakpointProperties;
import com.jetbrains.python.debugger.PyExceptionBreakpointType;
-import com.jetbrains.python.debugger.pydev.ProcessDebugger;
+import com.jetbrains.python.debugger.pydev.PyDebugCallback;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
import java.util.List;
@@ -52,8 +52,12 @@ public class PythonDebuggerTest extends PyEnvTestCase {
});
}
- public void testDebugger() {
- runPythonTest(new PyUnitTestTask("", "test_debug.py") {
+ public void testPydevTests_Debugger() {
+ unittests("tests_python/test_debugger.py");
+ }
+
+ private void unittests(final String script) {
+ runPythonTest(new PyUnitTestTask("", script) {
@Override
protected String getTestDataPath() {
return PythonHelpersLocator.getPythonCommunityPath() + "/helpers/pydev";
@@ -63,9 +67,18 @@ public class PythonDebuggerTest extends PyEnvTestCase {
public void after() {
allTestsPassed();
}
+
+ @Override
+ protected int getTestTimeout() {
+ return 600000;
+ }
});
}
+ public void testDebug() { //TODO: merge it into pydev tests
+ unittests("test_debug.py");
+ }
+
public void testConditionalBreakpoint() throws Exception {
runPythonTest(new PyDebuggerTask("/debug", "test1.py") {
@Override
@@ -128,7 +141,7 @@ public class PythonDebuggerTest extends PyEnvTestCase {
}
private void consoleExec(String command) {
- myDebugProcess.consoleExec(command, new ProcessDebugger.DebugCallback<String>() {
+ myDebugProcess.consoleExec(command, new PyDebugCallback<String>() {
@Override
public void ok(String value) {
diff --git a/python/testSrc/com/jetbrains/env/ut/PyUnitTestTask.java b/python/testSrc/com/jetbrains/env/ut/PyUnitTestTask.java
index 01401ca1ca9a..550ca6690cfd 100644
--- a/python/testSrc/com/jetbrains/env/ut/PyUnitTestTask.java
+++ b/python/testSrc/com/jetbrains/env/ut/PyUnitTestTask.java
@@ -1,8 +1,11 @@
package com.jetbrains.env.ut;
import com.google.common.collect.Lists;
-import com.intellij.execution.*;
+import com.intellij.execution.RunManager;
+import com.intellij.execution.RunManagerEx;
+import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
@@ -29,20 +32,37 @@ import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
import com.jetbrains.python.testing.AbstractPythonTestRunConfiguration;
import com.jetbrains.python.testing.PythonTestConfigurationType;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.junit.Assert;
/**
+ * Tasks to run unit test configurations.
+ * You should extend it either implementing {@link #after()} and {@link #before()} or implement {@link #runTestOn(String)}
+ * yourself and use {@link #runConfiguration(com.intellij.execution.configurations.ConfigurationFactory, String, com.intellij.openapi.project.Project)}
+ * or {@link #runConfiguration(com.intellij.execution.RunnerAndConfigurationSettings, com.intellij.execution.configurations.RunConfiguration)} .
+ * Use {@link #myDescriptor} and {@link #myConsoleView} to check output
+ *
* @author traff
*/
public abstract class PyUnitTestTask extends PyExecutionFixtureTestTask {
protected ProcessHandler myProcessHandler;
private boolean shouldPrintOutput = false;
- private SMTestProxy.SMRootTestProxy myTestProxy;
- private boolean mySetUp = false;
- private SMTRunnerConsoleView myConsoleView;
- private RunContentDescriptor myDescriptor;
+ /**
+ * Test root node
+ */
+ protected SMTestProxy.SMRootTestProxy myTestProxy;
+ /**
+ * Output test console
+ */
+ protected SMTRunnerConsoleView myConsoleView;
+ /**
+ * Test run descriptor
+ */
+ protected RunContentDescriptor myDescriptor;
+
private StringBuilder myOutput;
+ private boolean mySetUp = false;
public PyUnitTestTask() {
}
@@ -83,30 +103,30 @@ public abstract class PyUnitTestTask extends PyExecutionFixtureTestTask {
@Override
public void tearDown() throws Exception {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
- @Override
- public void run() {
- try {
- if (mySetUp) {
- if (myConsoleView != null) {
- Disposer.dispose(myConsoleView);
- myConsoleView = null;
- }
- if (myDescriptor != null) {
- Disposer.dispose(myDescriptor);
- myDescriptor = null;
- }
-
-
- PyUnitTestTask.super.tearDown();
-
- mySetUp = false;
- }
- }
- catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- }
+ @Override
+ public void run() {
+ try {
+ if (mySetUp) {
+ if (myConsoleView != null) {
+ Disposer.dispose(myConsoleView);
+ myConsoleView = null;
+ }
+ if (myDescriptor != null) {
+ Disposer.dispose(myDescriptor);
+ myDescriptor = null;
+ }
+
+
+ PyUnitTestTask.super.tearDown();
+
+ mySetUp = false;
+ }
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
);
}
@@ -151,10 +171,30 @@ public abstract class PyUnitTestTask extends PyExecutionFixtureTestTask {
}
}.execute();
- final ExecutionEnvironment environment = ExecutionEnvironmentBuilder.create(DefaultRunExecutor.getRunExecutorInstance(), settings).build();
+ runConfiguration(settings, config);
+ }
+
+ /**
+ * Run configuration.
+ *
+ * @param settings settings (if have any, null otherwise)
+ * @param config configuration to run
+ * @throws Exception
+ */
+ protected void runConfiguration(@Nullable final RunnerAndConfigurationSettings settings,
+ @NotNull final RunConfiguration config) throws Exception {
+ final ExecutionEnvironment environment;
+ if (settings == null) {
+ environment = ExecutionEnvironmentBuilder.create(DefaultRunExecutor.getRunExecutorInstance(), config).build();
+ }
+ else {
+ environment = ExecutionEnvironmentBuilder.create(DefaultRunExecutor.getRunExecutorInstance(), settings).build();
+ }
//noinspection ConstantConditions
+
Assert.assertTrue(environment.getRunner().canRun(DefaultRunExecutor.EXECUTOR_ID, config));
+
before();
final com.intellij.util.concurrency.Semaphore s = new com.intellij.util.concurrency.Semaphore();
@@ -177,7 +217,7 @@ public abstract class PyUnitTestTask extends PyExecutionFixtureTestTask {
myOutput.append(event.getText());
}
});
- myConsoleView = (com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView)descriptor.getExecutionConsole();
+ myConsoleView = (SMTRunnerConsoleView)descriptor.getExecutionConsole();
myTestProxy = myConsoleView.getResultsViewer().getTestsRootNode();
myConsoleView.getResultsViewer().addEventsListener(new TestResultsViewer.SMEventsAdapter() {
@Override
@@ -194,7 +234,7 @@ public abstract class PyUnitTestTask extends PyExecutionFixtureTestTask {
}
});
- Assert.assertTrue(s.waitFor(60000));
+ Assert.assertTrue(s.waitFor(getTestTimeout()));
XDebuggerTestUtil.waitForSwing();
@@ -207,6 +247,10 @@ public abstract class PyUnitTestTask extends PyExecutionFixtureTestTask {
disposeProcess(myProcessHandler);
}
+ protected int getTestTimeout() {
+ return 60000;
+ }
+
protected void configure(AbstractPythonTestRunConfiguration config) {
}
diff --git a/python/testSrc/com/jetbrains/python/PySmartEnterTest.java b/python/testSrc/com/jetbrains/python/PySmartEnterTest.java
index 09e0317051a0..e58988010645 100644
--- a/python/testSrc/com/jetbrains/python/PySmartEnterTest.java
+++ b/python/testSrc/com/jetbrains/python/PySmartEnterTest.java
@@ -184,4 +184,9 @@ public class PySmartEnterTest extends PyTestCase {
public void testWithExpressionMissing() {
doTest();
}
+
+ // PY-12877
+ public void testWithOnlyColonMissing() {
+ doTest();
+ }
}
diff --git a/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java b/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java
index 3539150731b6..220c08ef2d24 100644
--- a/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java
+++ b/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java
@@ -260,6 +260,14 @@ public class PyStatementMoverTest extends PyTestCase {
doTest();
}
+ public void testOutsideFromDict() {
+ doTest();
+ }
+
+ public void testSameLevelAsDict() {
+ doTest();
+ }
+
public void testWith() { // PY-5202
try {
setLanguageLevel(LanguageLevel.PYTHON27);
diff --git a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java
index e1fa67de63e4..73ab49e28659 100644
--- a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java
+++ b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java
@@ -174,6 +174,10 @@ public class PythonHighlightingTest extends PyTestCase {
doTest(true, false);
}
+ public void testMultipleEscapedBackslashes() {
+ doTest(true, false);
+ }
+
public void testUnsupportedFeaturesInPython3() {
doTest(LanguageLevel.PYTHON30, true, false);
}
diff --git a/python/testSrc/com/jetbrains/python/PythonKeywordCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonKeywordCompletionTest.java
index 109a1bddd19b..e64f02ad16c3 100644
--- a/python/testSrc/com/jetbrains/python/PythonKeywordCompletionTest.java
+++ b/python/testSrc/com/jetbrains/python/PythonKeywordCompletionTest.java
@@ -199,6 +199,17 @@ public class PythonKeywordCompletionTest extends PyTestCase {
assertContainsElements(doTestByText("for x i<caret>n y:\n pass]"), "in");
}
+ // PY-11375
+ public void testYieldExpression() {
+ assertContainsElements(doTestByText("def gen(): x = <caret>"), "yield");
+ assertDoesntContain(doTestByText("def gen(): x = 1 + <caret>"), "yield");
+ assertContainsElements(doTestByText("def gen(): x = 1 + (<caret>"), "yield");
+ assertContainsElements(doTestByText("def gen(): x **= <caret>"), "yield");
+ assertDoesntContain(doTestByText("def gen(): func(<caret>)"), "yield");
+ assertContainsElements(doTestByText("def gen(): func((<caret>"), "yield");
+ assertDoesntContain(doTestByText("def gen(): x = y<caret> = 42"), "yield");
+ }
+
public void testExceptAfterElse() {
assertDoesntContain(doTestByText("try:\n" +
" pass\n" +
diff --git a/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java
index 8a237b7dfb01..3528d7c6517d 100644
--- a/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java
+++ b/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java
@@ -18,8 +18,14 @@ package com.jetbrains.python.fixtures;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ex.QuickFixWrapper;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.actions.ConfigurationFromContext;
+import com.intellij.execution.actions.RunConfigurationProducer;
+import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.find.findUsages.CustomUsageSearcher;
import com.intellij.find.findUsages.FindUsagesOptions;
+import com.intellij.ide.DataManager;
+import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
@@ -56,6 +62,7 @@ import com.jetbrains.python.PythonTestUtil;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.impl.PyFileImpl;
import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
import org.jetbrains.annotations.NotNull;
@@ -64,7 +71,6 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
/**
* @author yole
@@ -92,12 +98,12 @@ public abstract class PyTestCase extends UsefulTestCase {
IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory();
TestFixtureBuilder<IdeaProjectTestFixture> fixtureBuilder = factory.createLightFixtureBuilder(getProjectDescriptor());
final IdeaProjectTestFixture fixture = fixtureBuilder.getFixture();
- myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(fixture,
- new LightTempDirTestFixtureImpl(true));
- myFixture.setUp();
+ myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(fixture,
+ new LightTempDirTestFixtureImpl(true));
+ myFixture.setUp();
- myFixture.setTestDataPath(getTestDataPath());
- }
+ myFixture.setTestDataPath(getTestDataPath());
+ }
protected String getTestDataPath() {
return PythonTestUtil.getTestDataPath();
@@ -148,8 +154,9 @@ public abstract class PyTestCase extends UsefulTestCase {
/**
* Searches for quickfix itetion by its class
+ *
* @param clazz quick fix class
- * @param <T> quick fix class
+ * @param <T> quick fix class
* @return quick fix or null if nothing found
*/
@Nullable
@@ -170,8 +177,8 @@ public abstract class PyTestCase extends UsefulTestCase {
}
- protected static void assertNotParsed(PyFile file) {
- assertNull(PARSED_ERROR_MSG, ((PyFileImpl)file).getTreeElement());
+ protected static void assertNotParsed(PyFile file) {
+ assertNull(PARSED_ERROR_MSG, ((PyFileImpl)file).getTreeElement());
}
/**
@@ -184,15 +191,23 @@ public abstract class PyTestCase extends UsefulTestCase {
}
/**
+ * @see #moveByText(com.intellij.testFramework.fixtures.CodeInsightTestFixture, String)
+ */
+ protected void moveByText(@NotNull final String testToFind) {
+ moveByText(myFixture, testToFind);
+ }
+
+ /**
* Finds some text and moves cursor to it (if found)
*
+ * @param fixture test fixture
* @param testToFind text to find
* @throws AssertionError if element not found
*/
- protected void moveByText(@NotNull final String testToFind) {
- final PsiElement element = myFixture.findElementByText(testToFind, PsiElement.class);
+ public static void moveByText(@NotNull final CodeInsightTestFixture fixture, @NotNull final String testToFind) {
+ final PsiElement element = fixture.findElementByText(testToFind, PsiElement.class);
assert element != null : "No element found by text: " + testToFind;
- myFixture.getEditor().getCaretModel().moveToOffset(element.getTextOffset());
+ fixture.getEditor().getCaretModel().moveToOffset(element.getTextOffset());
}
/**
@@ -263,4 +278,31 @@ public abstract class PyTestCase extends UsefulTestCase {
public static String getHelpersPath() {
return new File(PythonHelpersLocator.getPythonCommunityPath(), "helpers").getPath();
}
+
+ /**
+ * Creates run configuration from right click menu
+ *
+ * @param fixture test fixture
+ * @param expectedClass expected class of run configuration
+ * @param <C> expected class of run configuration
+ * @return configuration (if created) or null (otherwise)
+ */
+ @Nullable
+ public static <C extends RunConfiguration> C createRunConfigurationFromContext(
+ @NotNull final CodeInsightTestFixture fixture,
+ @NotNull final Class<C> expectedClass) {
+ final DataContext context = DataManager.getInstance().getDataContext(fixture.getEditor().getComponent());
+ for (final RunConfigurationProducer<?> producer : RunConfigurationProducer.EP_NAME.getExtensions()) {
+ final ConfigurationFromContext fromContext = producer.createConfigurationFromContext(ConfigurationContext.getFromContext(context));
+ if (fromContext == null) {
+ continue;
+ }
+ final C result = PyUtil.as(fromContext.getConfiguration(), expectedClass);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
}
+
diff --git a/python/testSrc/com/jetbrains/python/hierarchy/PyCallHierarchyTest.java b/python/testSrc/com/jetbrains/python/hierarchy/PyCallHierarchyTest.java
new file mode 100644
index 000000000000..f9f7e226f913
--- /dev/null
+++ b/python/testSrc/com/jetbrains/python/hierarchy/PyCallHierarchyTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.hierarchy;
+
+import com.intellij.codeInsight.TargetElementUtilBase;
+import com.intellij.ide.hierarchy.HierarchyBrowserBaseEx;
+import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
+import com.intellij.ide.hierarchy.HierarchyTreeStructure;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.fixtures.PyTestCase;
+import com.jetbrains.python.hierarchy.call.PyCalleeFunctionTreeStructure;
+import com.jetbrains.python.hierarchy.call.PyCallerFunctionTreeStructure;
+import com.jetbrains.python.psi.PyFunction;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author novokrest
+ */
+public class PyCallHierarchyTest extends PyTestCase {
+ private static final String CALLER_VERIFICATION_SUFFIX = "_caller_verification.xml";
+ private static final String CALLEE_VERIFICATION_SUFFIX = "_callee_verification.xml";
+
+ public static String dump(final HierarchyTreeStructure treeStructure, @Nullable HierarchyNodeDescriptor descriptor) {
+ StringBuilder s = new StringBuilder();
+ dump(treeStructure, descriptor, 0, s);
+ return s.toString();
+ }
+
+ private static void dump(final HierarchyTreeStructure treeStructure,
+ @Nullable HierarchyNodeDescriptor descriptor,
+ int level,
+ StringBuilder b) {
+ if (level > 10) {
+ for(int i = 0; i<level; i++) b.append(" ");
+ b.append("<Probably infinite part skipped>\n");
+ return;
+ }
+ if(descriptor==null) descriptor = (HierarchyNodeDescriptor)treeStructure.getRootElement();
+ for(int i = 0; i<level; i++) b.append(" ");
+ descriptor.update();
+ b.append("<node text=\"").append(descriptor.getHighlightedText().getText()).append("\"")
+ .append(treeStructure.getBaseDescriptor() == descriptor ? " base=\"true\"" : "");
+
+ final Object[] children = treeStructure.getChildElements(descriptor);
+ if(children.length>0) {
+ b.append(">\n");
+ for (Object o : children) {
+ HierarchyNodeDescriptor d = (HierarchyNodeDescriptor)o;
+ dump(treeStructure, d, level + 1, b);
+ }
+ for(int i = 0; i<level; i++) b.append(" ");
+ b.append("</node>\n");
+ } else {
+ b.append("/>\n");
+ }
+ }
+
+ private String getBasePath() {
+ return "hierarchy/call/Static/" + getTestName(false);
+ }
+
+ private void configureByFiles(String ... fileNames) {
+ String[] filePaths = new String[fileNames.length];
+ int i = 0;
+ for (String fileName: fileNames) {
+ filePaths[i] = getBasePath() + "/" + fileName;
+ i++;
+ }
+ myFixture.configureByFiles(filePaths);
+ }
+
+ private String getVerificationFilePath(final String suffix) {
+ return getTestDataPath() + "/" + getBasePath() + "/" + getTestName(false) + suffix;
+ }
+
+ private String getVerificationCallerFilePath() {
+ return getVerificationFilePath(CALLER_VERIFICATION_SUFFIX);
+ }
+
+ private String getVerificationCalleeFilePath() {
+ return getVerificationFilePath(CALLEE_VERIFICATION_SUFFIX);
+ }
+
+ private void checkHierarchyTreeStructure(PyFunction function) throws Exception {
+ final PyCallerFunctionTreeStructure callerStructure = new PyCallerFunctionTreeStructure(myFixture.getProject(), function,
+ HierarchyBrowserBaseEx.SCOPE_PROJECT);
+ assertSameLinesWithFile(getVerificationCallerFilePath(), dump(callerStructure, null));
+ final PyCalleeFunctionTreeStructure calleeStructure = new PyCalleeFunctionTreeStructure(myFixture.getProject(), function,
+ HierarchyBrowserBaseEx.SCOPE_PROJECT);
+ assertSameLinesWithFile(getVerificationCalleeFilePath(), dump(calleeStructure, null));
+ }
+
+ private void doTestCallHierarchy(String ... fileNames) throws Exception {
+ configureByFiles(fileNames);
+
+ final PsiElement targetElement = TargetElementUtilBase
+ .findTargetElement(myFixture.getEditor(),
+ TargetElementUtilBase.ELEMENT_NAME_ACCEPTED | TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED);
+ assert targetElement != null : "Cannot find referenced element";
+ assert targetElement instanceof PyFunction : "Referenced element is not PyFunction";
+
+ PyFunction function = (PyFunction) targetElement;
+ checkHierarchyTreeStructure(function);
+ }
+
+ public void testSimple() throws Exception {
+ doTestCallHierarchy("main.py");
+ }
+
+ public void testArgumentList() throws Exception {
+ doTestCallHierarchy("main.py", "file_1.py");
+ }
+
+ public void testDefaultValue() throws Exception {
+ doTestCallHierarchy("main.py");
+ }
+
+ public void testLambda() throws Exception {
+ doTestCallHierarchy("main.py", "file_1.py");
+ }
+
+ public void testNestedCall() throws Exception {
+ doTestCallHierarchy("main.py", "file_1.py");
+ }
+
+ public void testInheritance() throws Exception {
+ doTestCallHierarchy("main.py");
+ }
+
+ public void testOverriddenMethod() throws Exception {
+ doTestCallHierarchy("main.py", "file_1.py");
+ }
+
+ public void testInnerFunction() throws Exception {
+ doTestCallHierarchy("main.py");
+ }
+
+ public void testConstructor() throws Exception {
+ doTestCallHierarchy("main.py");
+ }
+
+ public void testParentheses() throws Exception {
+ doTestCallHierarchy("main.py", "file_1.py");
+ }
+} \ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyInlineLocalTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyInlineLocalTest.java
index 9a764b94169d..cf6ad38e075d 100644
--- a/python/testSrc/com/jetbrains/python/refactoring/PyInlineLocalTest.java
+++ b/python/testSrc/com/jetbrains/python/refactoring/PyInlineLocalTest.java
@@ -20,6 +20,7 @@ import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
+import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.refactoring.inline.PyInlineLocalHandler;
@@ -97,8 +98,19 @@ public class PyInlineLocalTest extends PyTestCase {
// PY-12409
public void testResultExceedsRightMargin() {
final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(myFixture.getProject());
- settings.WRAP_LONG_LINES = true;
+ final CommonCodeStyleSettings commonSettings = settings.getCommonSettings(PythonLanguage.getInstance());
+
+ final int oldRightMargin = settings.getRightMargin(PythonLanguage.getInstance());
+ final boolean oldWrapLongLines = commonSettings.WRAP_LONG_LINES;
+
settings.setRightMargin(PythonLanguage.getInstance(), 80);
- doTest();
+ commonSettings.WRAP_LONG_LINES = true;
+ try {
+ doTest();
+ }
+ finally {
+ commonSettings.WRAP_LONG_LINES = oldWrapLongLines;
+ settings.setRightMargin(PythonLanguage.getInstance(), oldRightMargin);
+ }
}
}
diff --git a/python/testSrc/com/jetbrains/python/sdkTools/PyTestSdkTools.java b/python/testSrc/com/jetbrains/python/sdkTools/PyTestSdkTools.java
index 2d97e747f00b..0b5cdadbbc43 100644
--- a/python/testSrc/com/jetbrains/python/sdkTools/PyTestSdkTools.java
+++ b/python/testSrc/com/jetbrains/python/sdkTools/PyTestSdkTools.java
@@ -136,7 +136,7 @@ public final class PyTestSdkTools {
final SkeletonVersionChecker checker = new SkeletonVersionChecker(0);
final PySkeletonRefresher refresher = new PySkeletonRefresher(project, null, sdk, skeletonsPath, null, null);
- final List<String> errors = refresher.regenerateSkeletons(checker, null);
+ final List<String> errors = refresher.regenerateSkeletons(checker);
Assert.assertThat("Errors found", errors, Matchers.empty());
}
}