diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml
index bc85ff87ab05..8ad59600a122 100644
--- a/python/pluginResources/intellij.python.community.impl.xml
+++ b/python/pluginResources/intellij.python.community.impl.xml
@@ -620,6 +620,9 @@
key="python.sdk.installation.notification.group"/>
+
+
diff --git a/python/pluginResources/messages/PyBundle.properties b/python/pluginResources/messages/PyBundle.properties
index e1a21b16504e..4c21a893ce41 100644
--- a/python/pluginResources/messages/PyBundle.properties
+++ b/python/pluginResources/messages/PyBundle.properties
@@ -1755,6 +1755,7 @@ pycharm.free.mode.upgrade.body=Unlock the full potential of your IDE! Boost prod
pycharm.free.mode.upgrade.button=Purchase Subscription
python.packaging.sync.packages=Syncing project dependencies
python.sdk.read.only=Python SDK ''{0}'' is read-only and operation cannot be performed.
+python.source.root.detection.notification.group=Python source root detection
read.only.python.sdk.system.wide.read.only.message=System Python packages are read-only. Select a different interpreter to modify packages.
tracecontext.detecting.poetry.executable=Detecting Poetry Executable
tracecontext.detecting.pip.executable=Detecting Pip Executable
diff --git a/python/python-psi-impl/resources/messages/PyPsiBundle.properties b/python/python-psi-impl/resources/messages/PyPsiBundle.properties
index 01bd40553cc2..40c556f741b4 100644
--- a/python/python-psi-impl/resources/messages/PyPsiBundle.properties
+++ b/python/python-psi-impl/resources/messages/PyPsiBundle.properties
@@ -1371,3 +1371,8 @@ INSP.unnecessary.cast.message=Unnecessary cast; type is already ''{0}''
# Quick fixes for PyUnnecessaryCastInspection
QFIX.remove.cast.call=Remove 'cast' call
+
+# Quick fix: mark directory as source root
+QFIX.add.source.root.notification.text=''{0}'' has been marked as source root.
+QFIX.add.source.root.notification.ok=Ok
+QFIX.add.source.root.notification.revert=Revert
diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyMarkDirectoryAsSourceRootQuickFix.kt b/python/src/com/jetbrains/python/inspections/quickfix/PyMarkDirectoryAsSourceRootQuickFix.kt
index 268a10f449f3..05dffce1f17e 100644
--- a/python/src/com/jetbrains/python/inspections/quickfix/PyMarkDirectoryAsSourceRootQuickFix.kt
+++ b/python/src/com/jetbrains/python/inspections/quickfix/PyMarkDirectoryAsSourceRootQuickFix.kt
@@ -7,6 +7,12 @@ import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.codeInspection.util.IntentionName
import com.intellij.icons.AllIcons
import com.intellij.ide.projectView.actions.MarkRootsManager
+import com.intellij.notification.Notification
+import com.intellij.notification.NotificationAction
+import com.intellij.notification.NotificationGroupManager
+import com.intellij.notification.NotificationType
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
@@ -51,6 +57,31 @@ internal class PyMarkDirectoryAsSourceRootQuickFix(
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
sourceRoot.markAsSourceRoot(module)
findCache(context)?.clearCache()
+ showNotification()
+ }
+
+ private fun showNotification() {
+ val message = PyPsiBundle.message("QFIX.add.source.root.notification.text", getPathName())
+ val group = NotificationGroupManager.getInstance().getNotificationGroup("Python source root detection")
+ val notification = group.createNotification(message, NotificationType.INFORMATION)
+
+ // Ok action just closes the notification
+ notification.addAction(NotificationAction.createSimpleExpiring(
+ PyPsiBundle.message("QFIX.add.source.root.notification.ok")
+ ) { /* no-op, expiring */ })
+
+ // Revert action removes the just added source root
+ notification.addAction(object : NotificationAction(PyPsiBundle.message("QFIX.add.source.root.notification.revert")) {
+ override fun actionPerformed(e: AnActionEvent, notification: Notification) {
+ ApplicationManager.getApplication().runWriteAction {
+ sourceRoot.unmarkAsSourceRoot(module)
+ }
+ findCache(context)?.clearCache()
+ notification.expire()
+ }
+ })
+
+ notification.notify(project)
}
private fun VirtualFile.markAsSourceRoot(module: Module) {
@@ -59,4 +90,14 @@ internal class PyMarkDirectoryAsSourceRootQuickFix(
entry.addSourceFolder(this, JavaSourceRootType.SOURCE)
model.commit()
}
+
+ private fun VirtualFile.unmarkAsSourceRoot(module: Module) {
+ val model = ModuleRootManager.getInstance(module).modifiableModel
+ val entry = MarkRootsManager.findContentEntry(model, this) ?: return
+ val toRemove = entry.sourceFolders.firstOrNull { it.file == this || it.url == this.url }
+ if (toRemove != null) {
+ entry.removeSourceFolder(toRemove)
+ }
+ model.commit()
+ }
}