diff --git a/python/ide/src/META-INF/PyCharmCorePlugin.xml b/python/ide/src/META-INF/PyCharmCorePlugin.xml
index c60c1bcb06df..9808080f4da0 100644
--- a/python/ide/src/META-INF/PyCharmCorePlugin.xml
+++ b/python/ide/src/META-INF/PyCharmCorePlugin.xml
@@ -18,5 +18,7 @@
+
+
diff --git a/python/src/com/jetbrains/python/PyCellUtil.java b/python/src/com/jetbrains/python/PyCellUtil.java
new file mode 100644
index 000000000000..e64aa3a54daa
--- /dev/null
+++ b/python/src/com/jetbrains/python/PyCellUtil.java
@@ -0,0 +1,74 @@
+// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.jetbrains.python;
+
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiComment;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.util.PsiTreeUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class PyCellUtil {
+
+ public static boolean hasCells(@NotNull PsiFile file) {
+ PsiElement el = file.getFirstChild();
+ while (el != null) {
+ if (isBlockCell(el)) {
+ return true;
+ }
+ el = el.getNextSibling();
+ }
+ return false;
+ }
+
+ @Nullable
+ public static PsiElement getCellStart(@NotNull PsiElement element) {
+ PsiElement el = element;
+ while (el != null && !isBlockCell(el)) {
+ el = PsiTreeUtil.prevLeaf(el);
+ }
+ if (el == null) {
+ return element.getContainingFile().getFirstChild();
+ }
+ return PsiTreeUtil.nextLeaf(el);
+ }
+
+ public static boolean isBlockCell(PsiElement element) {
+ return (element instanceof PsiComment) &&
+ (element.getText().startsWith("# %%") || element.getText().startsWith("#%%") || element.getText().startsWith("# ")) ||
+ element.getText().startsWith("# In[");
+ }
+
+ @NotNull
+ public static String getCodeInCell(@Nullable PsiElement element) {
+ StringBuilder text = new StringBuilder();
+ while (element != null && !isBlockCell(element)) {
+ text.append(element.getText());
+ element = element.getNextSibling();
+ }
+ return StringUtil.trim(text.toString());
+ }
+
+ public static PsiElement findNextCell(PsiElement startElement) {
+ PsiElement el = PsiTreeUtil.nextLeaf(PsiTreeUtil.getDeepestFirst(startElement));
+ while (el != null) {
+ if (isBlockCell(el)) {
+ return el;
+ }
+ el = PsiTreeUtil.nextLeaf(el);
+ }
+ return null;
+ }
+
+ public static PsiElement findPrevCell(PsiElement startElement) {
+ PsiElement el = PsiTreeUtil.prevLeaf(PsiTreeUtil.getDeepestFirst(startElement));
+ while (el != null) {
+ if (isBlockCell(el)) {
+ return el;
+ }
+ el = PsiTreeUtil.prevLeaf(el);
+ }
+ return null;
+ }
+}
diff --git a/python/src/com/jetbrains/python/PyCharmProfessionalAdvertiser.java b/python/src/com/jetbrains/python/PyCharmProfessionalAdvertiser.java
new file mode 100644
index 000000000000..a3860a627ef9
--- /dev/null
+++ b/python/src/com/jetbrains/python/PyCharmProfessionalAdvertiser.java
@@ -0,0 +1,133 @@
+// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.jetbrains.python;
+
+import com.intellij.ide.BrowserUtil;
+import com.intellij.lang.annotation.AnnotationHolder;
+import com.intellij.lang.annotation.Annotator;
+import com.intellij.notification.NotificationDisplayType;
+import com.intellij.notification.NotificationGroup;
+import com.intellij.notification.NotificationListener;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.FileIndexFacade;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.util.QualifiedName;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.psi.PyFromImportStatement;
+import com.jetbrains.python.psi.PyImportElement;
+import com.jetbrains.python.sdk.PythonSdkType;
+import org.intellij.lang.annotations.Language;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author traff
+ */
+public class PyCharmProfessionalAdvertiser implements Annotator {
+
+
+ @Language("HTML")
+ private static final String NOTIFICATIONS_TEXT =
+ "PyCharm Professional Edition has special support for it.";
+
+ private static final NotificationGroup BALLOON_NOTIFICATIONS = new NotificationGroup("PyCharm Professional Advertiser",
+ NotificationDisplayType.STICKY_BALLOON, false);
+ private static final Key DONT_SHOW_BALLOON = Key.create("showingPyCompatibilityAdvertiserBalloon");
+
+ @Override
+ public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
+ if (element instanceof PyFile) {
+
+ final PyFile pyFile = (PyFile)element;
+ final Project project = element.getProject();
+
+ final VirtualFile vFile = pyFile.getVirtualFile();
+ if (vFile != null && FileIndexFacade.getInstance(project).isInLibraryClasses(vFile)) {
+ return;
+ }
+
+ final Boolean showingFlag = project.getUserData(DONT_SHOW_BALLOON);
+ if (showingFlag != null && showingFlag.booleanValue()) {
+ return;
+ }
+
+ if (!moduleUsesPythonSdk(pyFile)) {
+ return;
+ }
+
+ if (PyCellUtil.hasCells(pyFile)) {
+ showInspectionAdvertisement(project, "code cells in editor");
+ }
+
+ if (containsImport(pyFile, "django")) {
+ showInspectionAdvertisement(project, "Django Framework");
+ }
+
+ if (containsImport(pyFile, "flask")) {
+ showInspectionAdvertisement(project, "Flask Framework");
+ }
+
+ if (containsImport(pyFile, "pyramid")) {
+ showInspectionAdvertisement(project, "Pyramid Framework");
+ }
+ }
+ }
+
+ private static boolean moduleUsesPythonSdk(@NotNull PyFile file) {
+ final Module module = ModuleUtilCore.findModuleForFile(file.getVirtualFile(), file.getProject());
+ if (module != null) {
+ return PythonSdkType.findPythonSdk(module) != null;
+ }
+ return false;
+ }
+
+ private static void showInspectionAdvertisement(@NotNull Project project, @NotNull String message) {
+ showSingletonNotification(project, "You are using " + message, NOTIFICATIONS_TEXT, NotificationType.INFORMATION,
+ (notification, event) -> {
+ if ("prof".equals(event.getDescription())) {
+ BrowserUtil.browse("https://www.jetbrains.com/pycharm/features/editions_comparison_matrix.html");
+ }
+ });
+ }
+
+ private static void showSingletonNotification(@NotNull Project project,
+ @NotNull String title,
+ @NotNull String htmlContent,
+ @NotNull NotificationType type,
+ @NotNull NotificationListener listener) {
+ project.putUserData(DONT_SHOW_BALLOON, true);
+ BALLOON_NOTIFICATIONS.createNotification(title, htmlContent, type, (notification, event) -> {
+ try {
+ listener.hyperlinkUpdate(notification, event);
+ }
+ finally {
+ notification.expire();
+ }
+ }).notify(project);
+ }
+
+ private static boolean containsImport(@NotNull PyFile file, String pkg) {
+ for (PyFromImportStatement importStatement : file.getFromImports()) {
+ final QualifiedName name = importStatement.getImportSourceQName();
+ if (name != null && name.toString().toLowerCase().contains(pkg)) {
+ return true;
+ }
+ }
+ for (PyImportElement importElement : file.getImportTargets()) {
+ final QualifiedName name = importElement.getImportedQName();
+ if (name != null && name.toString().toLowerCase().contains(pkg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @NotNull
+ private static PyCharmProfessionalAdvertiserSettings getSettings(@NotNull Project project) {
+ return ServiceManager.getService(project, PyCharmProfessionalAdvertiserSettings.class);
+ }
+}