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