Update sdk configurator and inspection to suggest creating venv using requirements.txt or setup.py (PY-44743)

GitOrigin-RevId: e46b817ea085bca4b538c84eed3cf6d4a0a75013
This commit is contained in:
Semyon Proshev
2020-08-27 18:30:50 +03:00
committed by intellij-monorepo-bot
parent 8182eead0d
commit d53595adcf
22 changed files with 685 additions and 106 deletions

View File

@@ -20,5 +20,6 @@
<orderEntry type="module" module-name="intellij.platform.debugger" />
<orderEntry type="module" module-name="intellij.platform.statistics" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.platform.vcs" />
</component>
</module>

View File

@@ -74,6 +74,13 @@
<notificationGroup id="PyCharm Professional Advertiser" displayType="STICKY_BALLOON" isLogByDefault="false"/>
<notificationGroup id="Python interpreter configuring" displayType="BALLOON"/>
<notificationGroup id="Packaging" displayType="BALLOON"/>
<ignoredFileProvider implementation="com.jetbrains.python.sdk.configuration.PyTemporarilyIgnoredFileProvider"/>
</extensions>
<extensions defaultExtensionNs="Pythonid">
<projectSdkConfigurationExtension implementation="com.jetbrains.python.sdk.configuration.PyRequirementsTxtOrSetupPySdkConfiguration"
id="requirementsTxtOrSetupPy"/>
</extensions>
<actions resource-bundle="messages.ActionsBundle">

View File

@@ -2,11 +2,7 @@
package com.jetbrains.python
import com.intellij.concurrency.SensitiveProgressWrapper
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationDisplayType
import com.intellij.notification.NotificationGroup
import com.intellij.notification.NotificationType
import com.intellij.openapi.application.AppUIExecutor
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.module.Module
@@ -24,11 +20,15 @@ import com.intellij.openapi.startup.StartupManager
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.use
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.DirectoryProjectConfigurator
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer
import org.jetbrains.concurrency.CancellablePromise
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.setReadyToUseSdk
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.setSdkUsingExtension
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.suppressTipAndInspectionsFor
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
import kotlin.streams.asSequence
/**
@@ -36,9 +36,13 @@ import kotlin.streams.asSequence
*/
internal class PythonSdkConfigurator : DirectoryProjectConfigurator {
companion object {
private val BALLOON_NOTIFICATIONS = NotificationGroup("Python interpreter configuring", NotificationDisplayType.BALLOON, true)
private val LOGGER = Logger.getInstance(PythonSdkConfigurator::class.java)
private fun getModule(moduleRef: Ref<Module>, project: Project): Module? {
val module = (moduleRef.get() ?: ModuleManager.getInstance(project).modules.firstOrNull())
return module.also { LOGGER.debug { "Module: $it" } }
}
private fun getDefaultProjectSdk(): Sdk? {
return ProjectRootManager.getInstance(ProjectManager.getInstance().defaultProject).projectSdk?.takeIf { it.sdkType is PythonSdkType }
}
@@ -52,24 +56,6 @@ internal class PythonSdkConfigurator : DirectoryProjectConfigurator {
private fun <T> guardIndicator(indicator: ProgressIndicator, computable: () -> T): T {
return ProgressManager.getInstance().runProcess(computable, SensitiveProgressWrapper(indicator))
}
private fun onEdt(project: Project, runnable: () -> Unit): CancellablePromise<*>? {
return AppUIExecutor.onUiThread().expireWith(PyDisposable.getInstance(project)).submit { runnable() }
}
private fun notifyAboutConfiguredSdk(project: Project, module: Module, sdk: Sdk) {
BALLOON_NOTIFICATIONS.createNotification(
PyBundle.message("sdk.has.been.configured.as.the.project.interpreter", sdk.name),
NotificationType.INFORMATION
).apply {
val configureSdkAction = NotificationAction.createSimpleExpiring(PyBundle.message("configure.python.interpreter")) {
PySdkPopupFactory.createAndShow(project, module)
}
addAction(configureSdkAction)
notify(project)
}
}
}
override fun configureProject(project: Project, baseDir: VirtualFile, moduleRef: Ref<Module>, isProjectCreatedWithWizard: Boolean) {
@@ -79,20 +65,26 @@ internal class PythonSdkConfigurator : DirectoryProjectConfigurator {
return
}
val module = getModule(moduleRef, project) ?: return
val extension = PyProjectSdkConfigurationExtension.EP_NAME.findFirstSafe { it.isApplicable(module) }
val lifetime = extension?.let { suppressTipAndInspectionsFor(module, it) }
StartupManager.getInstance(project).runWhenProjectIsInitialized {
ProgressManager.getInstance().run(
object : Task.Backgroundable(project, PyBundle.message("configuring.python.interpreter"), true) {
override fun run(indicator: ProgressIndicator) = configureSdk(project, indicator)
object : Task.Backgroundable(project, PyBundle.message("configuring.python.interpreter"), extension == null) {
override fun run(indicator: ProgressIndicator) = lifetime.use { configureSdk(project, module, extension, indicator) }
}
)
}
}
private fun configureSdk(project: Project, indicator: ProgressIndicator) {
private fun configureSdk(project: Project,
module: Module,
extension: PyProjectSdkConfigurationExtension?,
indicator: ProgressIndicator) {
indicator.isIndeterminate = true
val context = UserDataHolderBase()
val module = ModuleManager.getInstance(project).modules.firstOrNull().also { LOGGER.debug { "Module: $it" } } ?: return
val existingSdks = ProjectSdksModel().apply { reset(project) }.sdks.filter { it.sdkType is PythonSdkType }
if (indicator.isCanceled) return
@@ -101,11 +93,7 @@ internal class PythonSdkConfigurator : DirectoryProjectConfigurator {
LOGGER.debug("Looking for the previously used interpreter")
guardIndicator(indicator) { findExistingAssociatedSdk(module, existingSdks) }?.let {
LOGGER.debug { "The previously used interpreter: $it" }
onEdt(project) {
SdkConfigurationUtil.setDirectoryProjectSdk(project, it)
module.excludeInnerVirtualEnv(it)
notifyAboutConfiguredSdk(project, module, it)
}
setReadyToUseSdk(project, module, it)
return
}
@@ -118,12 +106,10 @@ internal class PythonSdkConfigurator : DirectoryProjectConfigurator {
val newSdk = it.setupAssociated(existingSdks, module.basePath) ?: return
LOGGER.debug { "Created virtual environment related to the project: $newSdk" }
onEdt(project) {
runInEdt {
SdkConfigurationUtil.addSdk(newSdk)
newSdk.associateWithModule(module, null)
SdkConfigurationUtil.setDirectoryProjectSdk(project, newSdk)
module.excludeInnerVirtualEnv(newSdk)
notifyAboutConfiguredSdk(project, module, newSdk)
setReadyToUseSdk(project, module, newSdk)
}
return
@@ -131,14 +117,18 @@ internal class PythonSdkConfigurator : DirectoryProjectConfigurator {
if (indicator.isCanceled) return
if (extension != null) {
setSdkUsingExtension(module, extension) { extension.createAndAddSdkForConfigurator(module) }
return
}
PySdkProvider.EP_NAME.extensions().asSequence().forEach { extension ->
indicator.text = extension.configureSdkProgressText
LOGGER.debug(extension.configureSdkProgressText)
guardIndicator(indicator) { extension.configureSdk(project, module, existingSdks) }?.let {
onEdt(project) {
runInEdt {
SdkConfigurationUtil.addSdk(it)
SdkConfigurationUtil.setDirectoryProjectSdk(project, it)
notifyAboutConfiguredSdk(project, module, it)
setReadyToUseSdk(project, module, it)
}
return
}
@@ -154,19 +144,15 @@ internal class PythonSdkConfigurator : DirectoryProjectConfigurator {
.filter { it.sdkType is PythonSdkType && PythonSdkUtil.isConda(it) && !it.isAssociatedWithAnotherModule(module) }
.firstOrNull()
}?.let {
onEdt(project) {
SdkConfigurationUtil.setDirectoryProjectSdk(project, it)
notifyAboutConfiguredSdk(project, module, it)
}
setReadyToUseSdk(project, module, it)
return
}
guardIndicator(indicator) { detectCondaEnvs(module, existingSdks, context).firstOrNull() }?.let {
val newSdk = it.setupAssociated(existingSdks, module.basePath) ?: return
onEdt(project) {
runInEdt {
SdkConfigurationUtil.addSdk(newSdk)
SdkConfigurationUtil.setDirectoryProjectSdk(project, newSdk)
notifyAboutConfiguredSdk(project, module, newSdk)
setReadyToUseSdk(project, module, newSdk)
}
return
}
@@ -178,10 +164,7 @@ internal class PythonSdkConfigurator : DirectoryProjectConfigurator {
LOGGER.debug("Looking for the default interpreter setting for a new project")
guardIndicator(indicator) { getDefaultProjectSdk() }?.let {
LOGGER.debug { "Default interpreter setting for a new project: $it" }
onEdt(project) {
SdkConfigurationUtil.setDirectoryProjectSdk(project, it)
notifyAboutConfiguredSdk(project, module, it)
}
setReadyToUseSdk(project, module, it)
return
}
@@ -191,10 +174,7 @@ internal class PythonSdkConfigurator : DirectoryProjectConfigurator {
LOGGER.debug("Looking for the previously used system-wide interpreter")
guardIndicator(indicator) { findExistingSystemWideSdk(existingSdks) }?.let {
LOGGER.debug { "Previously used system-wide interpreter: $it" }
onEdt(project) {
SdkConfigurationUtil.setDirectoryProjectSdk(project, it)
notifyAboutConfiguredSdk(project, module, it)
}
setReadyToUseSdk(project, module, it)
return
}
@@ -204,14 +184,12 @@ internal class PythonSdkConfigurator : DirectoryProjectConfigurator {
LOGGER.debug("Looking for a system-wide interpreter")
guardIndicator(indicator) { findDetectedSystemWideSdk(module, existingSdks, context) }?.let {
LOGGER.debug { "Detected system-wide interpreter: $it" }
onEdt(project) {
runInEdt {
SdkConfigurationUtil.createAndAddSDK(it.homePath!!, PythonSdkType.getInstance())?.apply {
LOGGER.debug { "Created system-wide interpreter: $this" }
SdkConfigurationUtil.setDirectoryProjectSdk(project, this)
notifyAboutConfiguredSdk(project, module, this)
setReadyToUseSdk(project, module, this)
}
}
}
}
}

View File

@@ -0,0 +1,184 @@
// Copyright 2000-2020 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.sdk.configuration
import com.intellij.CommonBundle
import com.intellij.codeInspection.util.IntentionName
import com.intellij.execution.ExecutionException
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.fileTypes.PlainTextFileType
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.use
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.IdeBorderFactory
import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.JBUI
import com.jetbrains.python.PyBundle
import com.jetbrains.python.packaging.PyPackageManagerImpl
import com.jetbrains.python.packaging.PyPackageUtil
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.add.PyAddNewVirtualEnvFromFilePanel
import java.awt.BorderLayout
import java.awt.Insets
import java.nio.file.Paths
import javax.swing.JComponent
import javax.swing.JPanel
class PyRequirementsTxtOrSetupPySdkConfiguration : PyProjectSdkConfigurationExtension {
private val LOGGER = Logger.getInstance(PyRequirementsTxtOrSetupPySdkConfiguration::class.java)
override fun isApplicable(module: Module): Boolean = getRequirementsTxtOrSetupPy(module) != null
override fun createAndAddSdkForConfigurator(module: Module) = createAndAddSdk(module)
override fun getIntentionName(module: Module): @IntentionName String {
return PyBundle.message("sdk.create.venv.suggestion", getRequirementsTxtOrSetupPy(module)?.name)
}
override fun createAndAddSdkForInspection(module: Module) = createAndAddSdk(module)
private fun createAndAddSdk(module: Module): Sdk? {
val existingSdks = ProjectJdkTable.getInstance().allJdks.asList()
val (location, chosenBaseSdk, requirementsTxtOrSetupPy) = askForEnvData(module, existingSdks) ?: return null
val baseSdk = installSdkIfNeeded(chosenBaseSdk!!, module, existingSdks) ?: return null
val systemIndependentLocation = FileUtil.toSystemIndependentName(location)
Disposer.newDisposable("Creating virtual environment").use {
PyTemporarilyIgnoredFileProvider.ignoreRoot(systemIndependentLocation, it)
return createVirtualEnv(module, baseSdk, location, requirementsTxtOrSetupPy, existingSdks)?.also {
PySdkSettings.instance.onVirtualEnvCreated(
baseSdk,
systemIndependentLocation,
module.basePath ?: module.project.basePath
)
}
}
}
private fun getRequirementsTxtOrSetupPy(module: Module) =
PyPackageUtil.findRequirementsTxt(module) ?: PyPackageUtil.findSetupPy(module)?.virtualFile
private fun askForEnvData(module: Module, existingSdks: List<Sdk>): PyAddNewVirtualEnvFromFilePanel.Data? {
val requirementsTxtOrSetupPy = getRequirementsTxtOrSetupPy(module) ?: return null
var permitted = false
var envData: PyAddNewVirtualEnvFromFilePanel.Data? = null
ApplicationManager.getApplication().invokeAndWait {
val dialog = Dialog(module, existingSdks, requirementsTxtOrSetupPy)
permitted = dialog.showAndGet()
envData = dialog.envData
LOGGER.debug("Dialog exit code: ${dialog.exitCode}, $permitted")
}
return if (permitted) envData else null
}
private fun createVirtualEnv(module: Module,
baseSdk: Sdk,
location: String,
requirementsTxtOrSetupPy: String,
existingSdks: List<Sdk>): Sdk? {
ProgressManager.progress(PyBundle.message("python.sdk.creating.virtualenv.title"))
LOGGER.debug("Creating virtual environment")
val path = try {
PyPackageManagerImpl.getInstance(baseSdk).createVirtualEnv(location, false)
}
catch (e: ExecutionException) {
showSdkExecutionException(baseSdk, e, PyBundle.message("python.sdk.failed.to.create.interpreter.title"))
return null
}
val basePath = module.basePath
LOGGER.debug("Setting up associated virtual environment: $path, $basePath")
val sdk = PyDetectedSdk(path).setupAssociated(existingSdks, basePath) ?: return null
ApplicationManager.getApplication().invokeAndWait {
LOGGER.debug("Adding associated virtual environment: $path, $basePath")
SdkConfigurationUtil.addSdk(sdk)
sdk.associateWithModule(module, null)
}
val requirementsTxtOrSetupPyFile = VfsUtil.findFile(Paths.get(requirementsTxtOrSetupPy), false)
if (requirementsTxtOrSetupPyFile == null) {
LOGGER.warn("File with dependencies is not found: $requirementsTxtOrSetupPy")
}
else {
ProgressManager.progress(PyBundle.message("python.packaging.installing.packages"))
LOGGER.debug("Installing packages")
try {
PyPackageManagerImpl.getInstance(sdk).install(emptyList(), getCommandForPipInstall(requirementsTxtOrSetupPyFile))
}
catch (e: ExecutionException) {
showSdkExecutionException(sdk, e, PyBundle.message("python.packaging.notification.title.install.packages.failed"))
}
}
return sdk
}
private fun getCommandForPipInstall(requirementsTxtOrSetupPy: VirtualFile): List<String> {
return if (requirementsTxtOrSetupPy.fileType == PlainTextFileType.INSTANCE) {
listOf("-r", getAbsPath(requirementsTxtOrSetupPy))
}
else {
listOf("-e", getAbsPath(requirementsTxtOrSetupPy.parent))
}
}
@NlsSafe
private fun getAbsPath(file: VirtualFile): String = file.toNioPath().toAbsolutePath().toString()
private class Dialog(module: Module,
existingSdks: List<Sdk>,
private val requirementsTxtOrSetupPy: VirtualFile) : DialogWrapper(module.project, false, IdeModalityType.PROJECT) {
private val panel = PyAddNewVirtualEnvFromFilePanel(module, existingSdks, requirementsTxtOrSetupPy)
val envData
get() = panel.envData
init {
title = PyBundle.message("python.sdk.creating.virtualenv.title")
init()
}
override fun createCenterPanel(): JComponent? {
return JPanel(BorderLayout()).apply {
val border = IdeBorderFactory.createEmptyBorder(Insets(4, 0, 6, 0))
val message = PyBundle.message("sdk.create.venv.permission", requirementsTxtOrSetupPy.name)
add(
JBUI.Panels.simplePanel(JBLabel(message)).withBorder(border),
BorderLayout.NORTH
)
add(panel, BorderLayout.CENTER)
}
}
override fun postponeValidation(): Boolean {
return false
}
override fun doValidateAll(): List<ValidationInfo> = panel.validateAll(CommonBundle.getOkButtonText())
}
}

View File

@@ -0,0 +1,43 @@
// Copyright 2000-2020 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.sdk.configuration
import com.intellij.openapi.Disposable
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.IgnoredFileDescriptor
import com.intellij.openapi.vcs.changes.IgnoredFileProvider
import com.jetbrains.python.PyBundle
import org.jetbrains.annotations.SystemIndependent
internal class PyTemporarilyIgnoredFileProvider : IgnoredFileProvider {
companion object {
private val LOGGER = Logger.getInstance(PyTemporarilyIgnoredFileProvider::class.java)
private val IGNORED_ROOTS = mutableSetOf<String>()
internal fun ignoreRoot(path: @SystemIndependent String, parent: Disposable) {
Disposer.register(
parent,
{
IGNORED_ROOTS.remove(path)
LOGGER.info("$path has been removed from ignored roots")
}
)
IGNORED_ROOTS.add(path)
LOGGER.info("$path has been added to ignored roots")
}
}
override fun isIgnoredFile(project: Project, filePath: FilePath): Boolean {
val path = filePath.path
return IGNORED_ROOTS.any { FileUtil.isAncestor(it, path, false) }
}
override fun getIgnoredFiles(project: Project): Set<IgnoredFileDescriptor> = emptySet()
override fun getIgnoredGroupDescription(): String = PyBundle.message("sdk.create.venv.ignored.files")
}

View File

@@ -399,7 +399,7 @@ python.sdk.conda.path=Conda executable:
python.sdk.select.location.for.conda.title=Select Location for Conda Environment
python.sdk.creating.conda.environment.title=Creating Conda Environment
python.sdk.select.location.for.virtualenv.title=Select Location for Virtual Environment
python.sdk.creating.virtualenv.title=Creating Virtual Environment
python.sdk.creating.virtualenv.title=Creating virtual environment
python.sdk.add.python.interpreter.title=Add Python Interpreter
python.sdk.python.interpreter.title.0=Python Interpreter: {0}
python.sdk.new.environment.kind=New {0} environment
@@ -494,6 +494,12 @@ sdk.paths.dialog.reload.paths=Reload List of Paths
sdk.paths.dialog.added.by.user.suffix=(added by user)
sdk.paths.dialog.removed.by.user.suffix=(removed by user)
sdk.create.venv.suggestion=Create a virtual environment using {0}
sdk.create.venv.dependencies.label=Dependencies:
sdk.create.venv.dependencies.chooser=Choose requirements.txt or setup.py
sdk.create.venv.permission=File {0} contains project dependencies. Would you like to create a virtual environment using it?
sdk.create.venv.ignored.files=Temporarily ignored files
sdk.create.venv.dialog.label.location=Location:
sdk.create.venv.dialog.label.inherit.global.site.packages=Inherit global site-packages
sdk.create.venv.conda.dialog.label.location=Location:
@@ -1053,8 +1059,9 @@ project.cannot.be.generated=Project can not be generated
error.in.project.generation=Error in Project Generation
sdk.has.been.configured.as.the.project.interpreter={0} has been configured as the project interpreter
configure.python.interpreter=Configure a Python Interpreter...
configuring.python.interpreter=Configuring a Python Interpreter
sdk.has.been.configured.notification.name=Configured Python interpreter
configure.python.interpreter=Configure a Python interpreter...
configuring.python.interpreter=Configuring a Python interpreter
configuring.interpreters.link=<html><a href="#">Configure Interpreters
looking.for.previous.interpreter=Looking for the previously used interpreter
looking.for.related.venv=Looking for a virtual environment related to the project

View File

@@ -1,6 +1,7 @@
package com.jetbrains.python.psi.search;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
@@ -110,11 +111,11 @@ public class PySearchUtilBase {
@Nullable
public static VirtualFile findLibDir(Sdk sdk) {
return findLibDir(sdk.getRootProvider().getFiles(OrderRootType.CLASSES));
return findLibDir(ReadAction.compute(() -> sdk.getRootProvider().getFiles(OrderRootType.CLASSES)));
}
public static VirtualFile findVirtualEnvLibDir(Sdk sdk) {
VirtualFile[] classVFiles = sdk.getRootProvider().getFiles(OrderRootType.CLASSES);
VirtualFile[] classVFiles = ReadAction.compute(() -> sdk.getRootProvider().getFiles(OrderRootType.CLASSES));
String homePath = sdk.getHomePath();
if (homePath != null) {
File root = PythonSdkUtil.getVirtualEnvRoot(homePath);

View File

@@ -4,6 +4,9 @@
<extensionPoint qualifiedName="Pythonid.pythonFlavorProvider"
interface="com.jetbrains.python.sdk.flavors.PythonFlavorProvider"
dynamic="true"/>
<extensionPoint qualifiedName="Pythonid.projectSdkConfigurationExtension"
interface="com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension"
dynamic="true"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
</extensions>

View File

@@ -10,8 +10,8 @@ val Module.rootManager: ModuleRootManager
val Module.baseDir: VirtualFile?
get() {
val moduleFile = moduleFile ?: return null
val roots = rootManager.contentRoots
val moduleFile = moduleFile ?: return roots.firstOrNull()
return roots.firstOrNull { VfsUtil.isAncestor(it, moduleFile, true) } ?: roots.firstOrNull()
}

View File

@@ -0,0 +1,54 @@
// Copyright 2000-2020 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.sdk.configuration
import com.intellij.codeInspection.util.IntentionName
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
interface PyProjectSdkConfigurationExtension {
companion object {
@JvmStatic
val EP_NAME = ExtensionPointName.create<PyProjectSdkConfigurationExtension>("Pythonid.projectSdkConfigurationExtension")
}
/**
* Called by sdk configurator and interpreter inspection
* to determine if an extension could configure or suggest an interpreter for the passed [module].
*
* First applicable extension is processed, others are ignored.
* If there is no applicable extension, configurator and inspection guess a suitable interpreter.
*
* Could be called from AWT hence should be as fast as possible.
*/
fun isApplicable(module: Module): Boolean
/**
* An implementation is responsible for interpreter setup and registration in IDE.
* In case of failures `null` should be returned, the implementation is responsible for errors displaying.
*
* Rule of thumb is to explicitly ask a user if sdk creation is desired and allowed.
*/
@RequiresBackgroundThread
fun createAndAddSdkForConfigurator(module: Module): Sdk?
/**
* Returned string is used as a quick fix name.
* Example: `Create a virtual environment using requirements.txt`.
*/
@IntentionName
fun getIntentionName(module: Module): String
/**
* An implementation is responsible for interpreter setup and registration in IDE.
* In case of failures `null` should be returned, the implementation is responsible for errors displaying.
*
* You're free here to create sdk immediately, without any user permission since quick fix is explicitly clicked.
*/
@RequiresBackgroundThread
fun createAndAddSdkForInspection(module: Module): Sdk?
}

View File

@@ -392,6 +392,7 @@
<notificationGroup id="Python Compatibility Inspection Advertiser" displayType="STICKY_BALLOON" isLogByDefault="false" bundle="messages.PyBundle" key="python.compatibility.inspection.advertiser.notifications.group.title"/>
<notificationGroup id="Package requirements" displayType="BALLOON" isLogByDefault="false"/>
<notificationGroup id="Pipfile Watcher" displayType="STICKY_BALLOON" isLogByDefault="false"/>
<notificationGroup id="ConfiguredPythonInterpreter" displayType="BALLOON" isLogByDefault="true" bundle="messages.PyBundle" key="sdk.has.been.configured.notification.name"/>
</extensions>
<extensionPoints>
@@ -537,6 +538,8 @@
<!-- Experimental packaging extensions-->
<pySdkProvider implementation="com.jetbrains.python.sdk.pipenv.PyPipEnvSdkProvider"/>
<packageManagerProvider implementation="com.jetbrains.python.sdk.pipenv.PyPipenvPackageManagerProvider"/>
<inspectionExtension implementation="com.jetbrains.python.sdk.configuration.PyInterpreterInspectionSuppressor"/>
</extensions>
<actions>

View File

@@ -6,6 +6,8 @@ import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.util.InspectionMessage;
import com.intellij.codeInspection.util.IntentionFamilyName;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtilCore;
@@ -33,9 +35,10 @@ import com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.sdk.*;
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration;
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension;
import com.jetbrains.python.ui.PyUiUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -136,9 +139,7 @@ public final class PyInterpreterInspection extends PyInspection {
private void registerProblemWithCommonFixes(PyFile node, @InspectionMessage String message, Module module, Sdk sdk, List<LocalQuickFix> fixes, boolean pyCharm) {
if (pyCharm && sdk == null) {
final String sdkName = ProjectRootManager.getInstance(node.getProject()).getProjectSdkName();
if (sdkName != null) {
ContainerUtil.addIfNotNull(fixes, getSuitableSdkFix(sdkName, module));
}
ContainerUtil.addIfNotNull(fixes, getSuitableSdkFix(sdkName, module));
}
if (pyCharm) {
fixes.add(new ConfigureInterpreterFix());
@@ -151,7 +152,7 @@ public final class PyInterpreterInspection extends PyInspection {
}
@Nullable
private static LocalQuickFix getSuitableSdkFix(@NotNull String name, @NotNull Module module) {
private static LocalQuickFix getSuitableSdkFix(@Nullable String name, @NotNull Module module) {
// this method is based on com.jetbrains.python.sdk.PySdkExtKt.suggestAssociatedSdkName
final List<Sdk> existingSdks = getExistingSdks();
@@ -167,6 +168,11 @@ public final class PyInterpreterInspection extends PyInspection {
if (detectedAssociatedSdk != null) return new UseDetectedInterpreterFix(detectedAssociatedSdk, existingSdks, true, module);
final var extension = PyProjectSdkConfigurationExtension.getEP_NAME().findFirstSafe(e -> e.isApplicable(module));
if (extension != null) return new UseProvidedInterpreterFix(module, extension);
if (name == null) return null;
final Matcher matcher = NAME.matcher(name);
if (!matcher.matches()) return null;
@@ -322,9 +328,9 @@ public final class PyInterpreterInspection extends PyInspection {
}
public static final class ConfigureInterpreterFix implements LocalQuickFix {
@NotNull
@Override
public String getFamilyName() {
public @IntentionFamilyName @NotNull String getFamilyName() {
return PyPsiBundle.message("INSP.interpreter.configure.python.interpreter");
}
@@ -345,6 +351,35 @@ public final class PyInterpreterInspection extends PyInspection {
}
}
private static final class UseProvidedInterpreterFix implements LocalQuickFix {
@NotNull
private final Module myModule;
@NotNull
private final PyProjectSdkConfigurationExtension myExtension;
private UseProvidedInterpreterFix(@NotNull Module module, @NotNull PyProjectSdkConfigurationExtension extension) {
myModule = module;
myExtension = extension;
}
@Override
public @IntentionFamilyName @NotNull String getFamilyName() {
return PyPsiBundle.message("INSP.interpreter.use.suggested.interpreter");
}
@Override
public @IntentionName @NotNull String getName() {
return myExtension.getIntentionName(myModule);
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PyProjectSdkConfiguration.INSTANCE.configureSdkUsingExtension(myModule, myExtension, () -> myExtension.createAndAddSdkForInspection(myModule));
}
}
private static abstract class UseInterpreterFix<T extends Sdk> implements LocalQuickFix {
@NotNull
@@ -354,15 +389,13 @@ public final class PyInterpreterInspection extends PyInspection {
mySdk = sdk;
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@Override
public @NotNull String getFamilyName() {
public @IntentionFamilyName @NotNull String getFamilyName() {
return PyPsiBundle.message("INSP.interpreter.use.suggested.interpreter");
}
@Nls(capitalization = Nls.Capitalization.Sentence)
@Override
public @NotNull String getName() {
public @IntentionName @NotNull String getName() {
return PyPsiBundle.message("INSP.interpreter.use.interpreter", PySdkPopupFactory.Companion.shortenNameInPopup(mySdk, 75));
}
@@ -385,8 +418,7 @@ public final class PyInterpreterInspection extends PyInspection {
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PyUiUtil.clearFileLevelInspectionResults(project);
SdkConfigurationUtil.setDirectoryProjectSdk(project, mySdk);
PySdkExtKt.excludeInnerVirtualEnv(myModule, mySdk);
PyProjectSdkConfiguration.INSTANCE.setReadyToUseSdk(project, myModule, mySdk);
}
}
@@ -420,8 +452,7 @@ public final class PyInterpreterInspection extends PyInspection {
SdkConfigurationUtil.addSdk(newSdk);
if (myAssociate) PySdkExtKt.associateWithModule(newSdk, myModule, null);
SdkConfigurationUtil.setDirectoryProjectSdk(project, newSdk);
if (myAssociate) PySdkExtKt.excludeInnerVirtualEnv(myModule, newSdk);
PyProjectSdkConfiguration.INSTANCE.setReadyToUseSdk(project, myModule, newSdk);
}
}
}

View File

@@ -7,6 +7,7 @@ import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
@@ -88,7 +89,7 @@ public final class PyPackageUtil {
for (VirtualFile root : PyUtil.getSourceRoots(module)) {
final VirtualFile child = root.findChild("setup.py");
if (child != null) {
final PsiFile file = PsiManager.getInstance(module.getProject()).findFile(child);
final PsiFile file = ReadAction.compute(() -> PsiManager.getInstance(module.getProject()).findFile(child));
if (file instanceof PyFile) {
return (PyFile)file;
}

View File

@@ -18,6 +18,7 @@ package com.jetbrains.python.sdk
import com.intellij.execution.ExecutionException
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.progress.ProgressManager
@@ -30,6 +31,7 @@ import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.io.FileUtil
@@ -130,14 +132,20 @@ fun createSdkByGenerateTask(generateSdkHomePath: Task.WithResult<String, Executi
)
}
catch (e: ExecutionException) {
val description = PyPackageManagementService.toErrorDescription(listOf(e), baseSdk) ?: return null
PackagesNotificationPanel.showError(PyBundle.message("python.sdk.failed.to.create.interpreter.title"), description)
showSdkExecutionException(baseSdk, e, PyBundle.message("python.sdk.failed.to.create.interpreter.title"))
return null
}
val suggestedName = suggestedSdkName ?: suggestAssociatedSdkName(homeFile.path, associatedProjectPath)
return SdkConfigurationUtil.setupSdk(existingSdks.toTypedArray(), homeFile,
PythonSdkType.getInstance(),
false, null, suggestedName) ?: return null
false, null, suggestedName)
}
fun showSdkExecutionException(sdk: Sdk?, e: ExecutionException, @NlsContexts.DialogTitle title: String) {
runInEdt {
val description = PyPackageManagementService.toErrorDescription(listOf(e), sdk) ?: return@runInEdt
PackagesNotificationPanel.showError(title, description)
}
}
fun Sdk.associateWithModule(module: Module?, newProjectPath: String?) {

View File

@@ -3,6 +3,7 @@ package com.jetbrains.python.sdk
import com.intellij.application.options.ReplacePathToMacroMap
import com.intellij.openapi.components.*
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.PathUtil
import com.intellij.util.SystemProperties
@@ -44,6 +45,11 @@ class PySdkSettings : PersistentStateComponent<PySdkSettings.State> {
state.PREFERRED_VIRTUALENV_BASE_SDK = value
}
fun onVirtualEnvCreated(baseSdk: Sdk, location: @SystemIndependent String, projectPath: @SystemIndependent String?) {
setPreferredVirtualEnvBasePath(location, projectPath)
preferredVirtualEnvBaseSdk = baseSdk.homePath
}
fun setPreferredVirtualEnvBasePath(value: @SystemIndependent String, projectPath: @SystemIndependent String?) {
val pathMap = ReplacePathToMacroMap().apply {
projectPath?.let {
@@ -78,7 +84,7 @@ class PySdkSettings : PersistentStateComponent<PySdkSettings.State> {
override fun getState(): State = state
override fun loadState(state: PySdkSettings.State) {
override fun loadState(state: State) {
XmlSerializerUtil.copyBean(state, this.state)
}

View File

@@ -18,9 +18,11 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.HtmlBuilder
import com.intellij.openapi.util.text.HtmlChunk.*
@@ -49,6 +51,16 @@ internal fun getSdksToInstall(): List<PySdkToInstall> {
else emptyList()
}
@RequiresEdt
fun installSdkIfNeeded(sdk: Sdk?, module: Module?, existingSdks: List<Sdk>): Sdk? {
return sdk.let { if (it is PySdkToInstall) it.install(module) { detectSystemWideSdks(module, existingSdks) } else it }
}
@RequiresEdt
fun installSdkIfNeeded(sdk: Sdk?, module: Module?, existingSdks: List<Sdk>, context: UserDataHolder): Sdk? {
return sdk.let { if (it is PySdkToInstall) it.install(module) { detectSystemWideSdks(module, existingSdks, context) } else it }
}
private fun getPy37ToInstallOnWindows(): PySdkToInstallOnWindows {
val version = "3.7"
val name = "Python $version"

View File

@@ -0,0 +1,84 @@
// 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.sdk.add
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.fileTypes.PlainTextFileType
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ui.FormBuilder
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonFileType
import com.jetbrains.python.sdk.PySdkSettings
import com.jetbrains.python.sdk.basePath
import org.jetbrains.annotations.SystemDependent
import org.jetbrains.annotations.SystemIndependent
import java.awt.BorderLayout
import javax.swing.JPanel
class PyAddNewVirtualEnvFromFilePanel(private val module: Module,
existingSdks: List<Sdk>,
suggestedRequirementsTxtOrSetupPy: VirtualFile? = null) : JPanel() {
val envData: Data
get() = Data(pathField.text, baseSdkField.selectedSdk, requirementsTxtOrSetupPyField.text)
private val baseSdkField = PySdkPathChoosingComboBox()
private val pathField = TextFieldWithBrowseButton()
private val requirementsTxtOrSetupPyField = TextFieldWithBrowseButton()
private val projectBasePath: @SystemIndependent String?
get() = module.basePath ?: module.project.basePath
init {
pathField.apply {
text = FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(projectBasePath))
addBrowseFolderListener(
PyBundle.message("python.sdk.select.location.for.virtualenv.title"),
null,
module.project,
FileChooserDescriptorFactory.createSingleFolderDescriptor())
}
requirementsTxtOrSetupPyField.apply {
suggestedRequirementsTxtOrSetupPy?.path?.also {
text = FileUtil.toSystemDependentName(it)
setTextFieldPreferredWidth(it.length)
}
addBrowseFolderListener(
PyBundle.message("sdk.create.venv.dependencies.chooser"),
null,
module.project,
FileChooserDescriptorFactory.createSingleFileDescriptor().withFileFilter { file ->
file.fileType.let { it == PlainTextFileType.INSTANCE || it == PythonFileType.INSTANCE }
}
)
}
layout = BorderLayout()
val formPanel = FormBuilder.createFormBuilder()
.addLabeledComponent(PyBundle.message("sdk.create.venv.dialog.label.location"), pathField)
.addLabeledComponent(PyBundle.message("base.interpreter"), baseSdkField)
.addLabeledComponent(PyBundle.message("sdk.create.venv.dependencies.label"), requirementsTxtOrSetupPyField)
.panel
add(formPanel, BorderLayout.NORTH)
addBaseInterpretersAsync(baseSdkField, existingSdks, module, UserDataHolderBase())
}
fun validateAll(defaultButtonName: @NlsContexts.Button String): List<ValidationInfo> =
listOfNotNull(PyAddSdkPanel.validateEnvironmentDirectoryLocation(pathField),
PyAddSdkPanel.validateSdkComboBox(baseSdkField, defaultButtonName))
data class Data(
val path: @NlsSafe @SystemDependent String,
val baseSdk: Sdk?,
val requirementsTxtOrSetupPyPath: @NlsSafe @SystemDependent String
)
}

View File

@@ -75,8 +75,7 @@ class PyAddNewVirtualEnvPanel(private val project: Project?,
override fun getOrCreateSdk(): Sdk? {
val root = pathField.text
val baseSdk = baseSdkField.selectedSdk
.let { if (it is PySdkToInstall) it.install(module) { detectSystemWideSdks(module, existingSdks, context) } else it }
val baseSdk = installSdkIfNeeded(baseSdkField.selectedSdk, module, existingSdks, context)
if (baseSdk == null) return null
val task = object : Task.WithResult<String, ExecutionException>(project, PyBundle.message("python.sdk.creating.virtualenv.title"), false) {
@@ -93,10 +92,7 @@ class PyAddNewVirtualEnvPanel(private val project: Project?,
sdk.associateWithModule(module, newProjectPath)
}
moduleToExcludeSdkFrom(root, project)?.excludeInnerVirtualEnv(sdk)
with(PySdkSettings.instance) {
setPreferredVirtualEnvBasePath(FileUtil.toSystemIndependentName(pathField.text), projectBasePath)
preferredVirtualEnvBaseSdk = baseSdk.homePath
}
PySdkSettings.instance.onVirtualEnvCreated(baseSdk, FileUtil.toSystemIndependentName(root), projectBasePath)
return sdk
}

View File

@@ -76,7 +76,7 @@ abstract class PyAddSdkPanel : JPanel(), PyAddSdkView {
companion object {
@JvmStatic
protected fun validateEnvironmentDirectoryLocation(field: TextFieldWithBrowseButton): ValidationInfo? {
fun validateEnvironmentDirectoryLocation(field: TextFieldWithBrowseButton): ValidationInfo? {
val text = field.text
val file = File(text)
val message = when {
@@ -90,10 +90,15 @@ abstract class PyAddSdkPanel : JPanel(), PyAddSdkView {
@JvmStatic
protected fun validateSdkComboBox(field: PySdkPathChoosingComboBox, view: PyAddSdkView): ValidationInfo? {
return validateSdkComboBox(field, getDefaultButtonName(view))
}
@JvmStatic
fun validateSdkComboBox(field: PySdkPathChoosingComboBox, defaultButtonName: @NlsContexts.Button String): ValidationInfo? {
return when (val sdk = field.selectedSdk) {
null -> ValidationInfo(PyBundle.message("python.sdk.interpreter.field.is.empty"), field)
is PySdkToInstall -> {
val message = sdk.getInstallationWarning(getDefaultButtonName(view))
val message = sdk.getInstallationWarning(defaultButtonName)
ValidationInfo(message).asWarning().withOKEnabled()
}
else -> null

View File

@@ -57,8 +57,7 @@ class PyAddSystemWideInterpreterPanel(private val module: Module?,
override fun validateAll(): List<ValidationInfo> = listOfNotNull(validateSdkComboBox(sdkComboBox, this))
override fun getOrCreateSdk(): Sdk? {
return when (val sdk = sdkComboBox.selectedSdk) {
is PySdkToInstall -> sdk.install(module) { detectSystemWideSdks(module, existingSdks, context) }?.setup(existingSdks)
return when (val sdk = installSdkIfNeeded(sdkComboBox.selectedSdk, module, existingSdks, context)) {
is PyDetectedSdk -> sdk.setup(existingSdks)
else -> sdk
}

View File

@@ -0,0 +1,165 @@
// Copyright 2000-2020 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.sdk.configuration
import com.intellij.ide.GeneralSettings
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task.Backgroundable
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.use
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PyDisposable
import com.jetbrains.python.inspections.PyInspectionExtension
import com.jetbrains.python.inspections.PyPackageRequirementsInspection
import com.jetbrains.python.psi.PyFile
import com.jetbrains.python.sdk.PySdkPopupFactory
import com.jetbrains.python.sdk.excludeInnerVirtualEnv
import com.jetbrains.python.ui.PyUiUtil
object PyProjectSdkConfiguration {
private val LOGGER = Logger.getInstance(PyProjectSdkConfiguration::class.java)
fun configureSdkUsingExtension(module: Module, extension: PyProjectSdkConfigurationExtension, supplier: () -> Sdk?) {
val lifetime = suppressTipAndInspectionsFor(module, extension)
ProgressManager.getInstance().run(
object : Backgroundable(module.project, PyBundle.message("configuring.python.interpreter"), false) {
override fun run(indicator: ProgressIndicator) {
indicator.isIndeterminate = true
lifetime.use { setSdkUsingExtension(module, extension, supplier) }
}
}
)
}
@RequiresBackgroundThread
fun setSdkUsingExtension(module: Module, extension: PyProjectSdkConfigurationExtension, supplier: () -> Sdk?) {
ProgressManager.progress("")
LOGGER.debug("Configuring sdk with ${extension.javaClass.canonicalName} extension")
supplier()?.let { setReadyToUseSdk(module.project, module, it) }
}
fun setReadyToUseSdk(project: Project, module: Module, sdk: Sdk) {
runInEdt {
SdkConfigurationUtil.setDirectoryProjectSdk(project, sdk)
module.excludeInnerVirtualEnv(sdk)
notifyAboutConfiguredSdk(project, module, sdk)
}
}
fun suppressTipAndInspectionsFor(module: Module, extension: PyProjectSdkConfigurationExtension): Disposable {
val project = module.project
val lifetime = Disposer.newDisposable(
PyDisposable.getInstance(project),
"Configuring sdk using ${extension.javaClass.name} extension"
)
TipOfTheDaySuppressor.suppress()?.let { Disposer.register(lifetime, it) }
PyInterpreterInspectionSuppressor.suppress(project)?.let { Disposer.register(lifetime, it) }
Disposer.register(lifetime, PyPackageRequirementsInspectionSuppressor(module))
return lifetime
}
private fun notifyAboutConfiguredSdk(project: Project, module: Module, sdk: Sdk) {
NotificationGroupManager.getInstance().getNotificationGroup("ConfiguredPythonInterpreter").createNotification(
PyBundle.message("sdk.has.been.configured.as.the.project.interpreter", sdk.name),
NotificationType.INFORMATION
).apply {
val configureSdkAction = NotificationAction.createSimpleExpiring(PyBundle.message("configure.python.interpreter")) {
PySdkPopupFactory.createAndShow(project, module)
}
addAction(configureSdkAction)
notify(project)
}
}
}
private class TipOfTheDaySuppressor private constructor() : Disposable {
private val savedValue: Boolean
companion object {
private val LOGGER = Logger.getInstance(TipOfTheDaySuppressor::class.java)
fun suppress(): Disposable? {
return if (!GeneralSettings.getInstance().isShowTipsOnStartup) null else TipOfTheDaySuppressor()
}
}
init {
val settings = GeneralSettings.getInstance()
savedValue = settings.isShowTipsOnStartup
settings.isShowTipsOnStartup = false
LOGGER.info("Tip of the day has been disabled")
}
override fun dispose() {
val settings = GeneralSettings.getInstance()
if (!settings.isShowTipsOnStartup) { // nothing has been changed between init and dispose
settings.isShowTipsOnStartup = savedValue
LOGGER.info("Tip of the day has been enabled")
}
else {
LOGGER.info("Tip of the day was enabled somewhere else")
}
}
}
private class PyInterpreterInspectionSuppressor : PyInspectionExtension() {
companion object {
private val LOGGER = Logger.getInstance(PyInterpreterInspectionSuppressor::class.java)
private var suppress = false
fun suppress(project: Project): Disposable? {
PyUiUtil.clearFileLevelInspectionResults(project)
return if (suppress) null else Suppressor()
}
}
override fun ignoreInterpreterWarnings(file: PyFile): Boolean = suppress
private class Suppressor : Disposable {
init {
suppress = true
LOGGER.info("Interpreter warnings have been disabled")
}
override fun dispose() {
suppress = false
LOGGER.info("Interpreter warnings have been enabled")
}
}
}
private class PyPackageRequirementsInspectionSuppressor(module: Module): Disposable {
private val listener = PyPackageRequirementsInspection.RunningPackagingTasksListener(module)
init {
listener.started()
}
override fun dispose() {
listener.finished(emptyList())
}
}

View File

@@ -19,7 +19,6 @@ import com.intellij.notification.NotificationGroup
import com.intellij.notification.NotificationListener
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.event.DocumentEvent
@@ -44,11 +43,9 @@ import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.PathUtil
import com.intellij.webcore.packaging.PackagesNotificationPanel
import com.jetbrains.python.PyBundle
import com.jetbrains.python.inspections.PyPackageRequirementsInspection
import com.jetbrains.python.packaging.*
import com.jetbrains.python.packaging.ui.PyPackageManagementService
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
import icons.PythonIcons
@@ -383,13 +380,7 @@ class PipEnvPipFileWatcher : EditorFactoryListener {
catch (e: RunCanceledByUserException) {
}
catch (e: ExecutionException) {
runInEdt {
val error = PyPackageManagementService.toErrorDescription(listOf(e), module.pythonSdk)
if (error != null) {
PackagesNotificationPanel.showError(
PyBundle.message("python.sdk.pipenv.execution.exception.error.running.pipenv.message"), error)
}
}
showSdkExecutionException(sdk, e, PyBundle.message("python.sdk.pipenv.execution.exception.error.running.pipenv.message"))
}
finally {
PythonSdkUtil.getSitePackagesDirectory(sdk)?.refresh(true, true)