mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
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:
committed by
intellij-monorepo-bot
parent
8182eead0d
commit
d53595adcf
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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?
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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?) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user