diff --git a/python/ide/src/META-INF/PyCharmCorePlugin.xml b/python/ide/src/META-INF/PyCharmCorePlugin.xml
index c60c1bcb06df..04ca94cc22ab 100644
--- a/python/ide/src/META-INF/PyCharmCorePlugin.xml
+++ b/python/ide/src/META-INF/PyCharmCorePlugin.xml
@@ -18,5 +18,8 @@
+
+
+
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..1bcfc8ecafc0
--- /dev/null
+++ b/python/src/com/jetbrains/python/PyCharmProfessionalAdvertiser.java
@@ -0,0 +1,122 @@
+// 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.project.Project;
+import com.intellij.openapi.roots.FileIndexFacade;
+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 org.intellij.lang.annotations.Language;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @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);
+ @Override
+ public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
+
+ if (element instanceof PyFile) {
+
+ final PyFile pyFile = (PyFile)element;
+ final Project project = element.getProject();
+
+ if (getSettings(project).shown) {
+ return;
+ }
+
+ final VirtualFile vFile = pyFile.getVirtualFile();
+ if (vFile != null && FileIndexFacade.getInstance(project).isInLibraryClasses(vFile)) {
+ return;
+ }
+
+ if (PyCellUtil.hasCells(pyFile)) {
+ showInspectionAdvertisement(project, "code cells in the editor", "https://www.jetbrains.com/pycharm/features/scientific_tools.html", "codecells");
+ }
+
+ if (containsImport(pyFile, "django")) {
+ showInspectionAdvertisement(project, "the Django Framework", "https://www.jetbrains.com/pycharm/features/web_development.html#django","django");
+ }
+
+ if (containsImport(pyFile, "flask")) {
+ showInspectionAdvertisement(project, "the Flask Framework", null,"flask");
+ }
+
+ if (containsImport(pyFile, "pyramid")) {
+ showInspectionAdvertisement(project, "the Pyramid Framework", null,"pyramid");
+ }
+ }
+ }
+
+ private static void showInspectionAdvertisement(@NotNull Project project,
+ @NotNull String message,
+ @Nullable String url,
+ @NotNull String source) {
+ showSingletonNotification(project, "You are using " + message, NOTIFICATIONS_TEXT, NotificationType.INFORMATION,
+ (notification, event) -> {
+ if ("prof".equals(event.getDescription())) {
+ BrowserUtil.browse(
+ (url != null ? url : "https://www.jetbrains.com/pycharm/features/editions_comparison_matrix.html") +
+ "?utm_source=from_product&utm_medium=advertiser&utm_campaign=" +
+ source);
+ }
+ });
+ }
+
+ private static void showSingletonNotification(@NotNull Project project,
+ @NotNull String title,
+ @NotNull String htmlContent,
+ @NotNull NotificationType type,
+ @NotNull NotificationListener listener) {
+ getSettings(project).shown = 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);
+ }
+}
diff --git a/python/src/com/jetbrains/python/PyCharmProfessionalAdvertiserSettings.java b/python/src/com/jetbrains/python/PyCharmProfessionalAdvertiserSettings.java
new file mode 100644
index 000000000000..7f09a927adc3
--- /dev/null
+++ b/python/src/com/jetbrains/python/PyCharmProfessionalAdvertiserSettings.java
@@ -0,0 +1,27 @@
+// 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.components.PersistentStateComponent;
+import com.intellij.openapi.components.State;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author traff
+ */
+@State(name = "PyCharmProfessionalAdvertiser")
+public class PyCharmProfessionalAdvertiserSettings implements PersistentStateComponent {
+ public boolean shown = false;
+
+ @Nullable
+ @Override
+ public PyCharmProfessionalAdvertiserSettings getState() {
+ return this;
+ }
+
+ @Override
+ public void loadState(@NotNull PyCharmProfessionalAdvertiserSettings state) {
+ XmlSerializerUtil.copyBean(state, this);
+ }
+}