PY-50536 [NPW] Added New Python project wizard for IntelliJ

GitOrigin-RevId: ef728d8ec331182f18ee19948d96dd7e3c89291f
This commit is contained in:
Andrey Vlasovskikh
2021-10-12 16:59:35 +03:00
committed by intellij-monorepo-bot
parent c724623599
commit ca954ebf09
4 changed files with 268 additions and 1 deletions

View File

@@ -5,6 +5,7 @@
<xi:include href="/META-INF/python-community-plugin-core.xml" xpointer="xpointer(/idea-plugin/*)"/>
<extensions defaultExtensionNs="com.intellij">
<newProjectWizard.language implementation="com.jetbrains.python.newProject.PythonNewProjectWizard"/>
<moduleType id="PYTHON_MODULE" implementationClass="com.jetbrains.python.module.PythonModuleType"/>
<facetType implementation="com.jetbrains.python.facet.PythonFacetType"/>
<framework.detector implementation="com.jetbrains.python.facet.PythonFacetType$PythonFrameworkDetector"/>

View File

@@ -390,6 +390,8 @@ python.sdk.pipenv.pip.file.notification.content=Run <a href='#lock'>pipenv lock<
python.sdk.pipenv.pip.file.notification.locking=Locking Pipfile
python.sdk.pipenv.pip.file.notification.updating=Updating Pipenv environment
python.sdk.pipenv.pip.file.watcher=Pipfile Watcher
python.sdk.new.project.environment=Environment:
python.sdk.new.project.environment.type=Environment type:
python.sdk.file.not.found=File {0} is not found
python.sdk.cannot.execute=Cannot execute {0}

View File

@@ -0,0 +1,255 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. 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.newProject
import com.intellij.ide.highlighter.ModuleFileType
import com.intellij.ide.util.projectWizard.WizardContext
import com.intellij.ide.wizard.*
import com.intellij.openapi.Disposable
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.observable.properties.GraphProperty
import com.intellij.openapi.observable.properties.GraphPropertyImpl.Companion.graphProperty
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel
import com.intellij.openapi.util.Disposer
import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.gridLayout.HorizontalAlign
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonModuleTypeBase
import com.jetbrains.python.newProject.steps.ProjectSpecificSettingsStep
import com.jetbrains.python.newProject.steps.PyAddExistingSdkPanel
import com.jetbrains.python.sdk.PySdkProvider
import com.jetbrains.python.sdk.PySdkSettings
import com.jetbrains.python.sdk.add.PyAddNewCondaEnvPanel
import com.jetbrains.python.sdk.add.PyAddNewVirtualEnvPanel
import com.jetbrains.python.sdk.add.PyAddSdkPanel
import com.jetbrains.python.sdk.pythonSdk
import kotlin.streams.toList
/**
* A wizard for creating new pure-Python projects in IntelliJ.
*
* It suggests creating a new Python virtual environment for your new project to follow Python best practices.
*/
class PythonNewProjectWizard : LanguageNewProjectWizard {
override val name: String = "Python"
override fun createStep(parent: NewProjectWizardLanguageStep): NewProjectWizardStep = NewPythonProjectStep(parent)
}
/**
* Data for sharing among the steps of the new Python project wizard.
*/
interface NewProjectWizardPythonData : NewProjectWizardBaseData {
/**
* A property for tracking changes in [pythonSdk].
*/
val pythonSdkProperty: GraphProperty<Sdk?>
/**
* The Python SDK for the new Python project or module.
*
* During [NewProjectWizardStep.setupUI] it reflects the selected Python SDK (it may be `null` for a new environment or if there is no
* Python installed on the machine). After [PythonSdkStep] gets or creates the actual SDK for the new project in its
* [NewProjectWizardStep.setupProject], the attribute contains the actual SDK.
*/
var pythonSdk: Sdk?
/**
* The Python module after it has been created during [NewProjectWizardStep.setupProject].
*/
val module: Module?
}
/**
* A new Python project wizard step that allows you to either create a new Python environment or select an existing Python interpreter.
*
* It works for both PyCharm (where the *.iml file resides in .idea/ directory and the SDK is set for the project) and other
* IntelliJ-based IDEs (where the *.iml file resides in the module directory and the SDK is set for the module).
*/
class NewPythonProjectStep<P>(parent: P)
: AbstractNewProjectWizardStep(parent),
NewProjectWizardBaseData by parent,
NewProjectWizardPythonData
where P : NewProjectWizardStep, P : NewProjectWizardBaseData {
override val pythonSdkProperty: GraphProperty<Sdk?> = propertyGraph.graphProperty { null }
override var pythonSdk: Sdk? by pythonSdkProperty
override val module: Module?
get() = intellijModule ?: context.project?.let { ModuleManager.getInstance(it).modules.firstOrNull() }
private var intellijModule: Module? = null
private val sdkStep: PythonSdkStep<NewPythonProjectStep<P>> by lazy { PythonSdkStep(this) }
override fun setupUI(builder: Panel) {
sdkStep.setupUI(builder)
}
override fun setupProject(project: Project) {
commitIntellijModule(project)
sdkStep.setupProject(project)
setupSdk(project)
}
private fun commitIntellijModule(project: Project) {
val moduleName = name
val moduleBuilder = PythonModuleTypeBase.getInstance().createModuleBuilder().apply {
name = moduleName
contentEntryPath = projectPath.toString()
moduleFilePath = projectPath.resolve(moduleName + ModuleFileType.DOT_DEFAULT_EXTENSION).toString()
}
intellijModule = moduleBuilder.commit(project)?.firstOrNull()
}
private fun setupSdk(project: Project) {
var sdk = pythonSdk ?: return
val existingSdk = ProjectJdkTable.getInstance().findJdk(sdk.name)
if (existingSdk != null) {
pythonSdk = existingSdk
sdk = existingSdk
}
else {
SdkConfigurationUtil.addSdk(sdk)
}
val module = intellijModule
if (module != null) {
module.pythonSdk = sdk
}
else {
SdkConfigurationUtil.setDirectoryProjectSdk(project, sdk)
}
}
}
/**
* A new Python project wizard step that allows you to get or create a Python SDK for your [projectPath].
*
* The resulting SDK is available as [pythonSdk]. The SDK may have not been saved to the project JDK table yet.
*/
class PythonSdkStep<P>(parent: P)
: AbstractNewProjectWizardMultiStepBase(parent),
NewProjectWizardPythonData by parent
where P : NewProjectWizardStep, P : NewProjectWizardPythonData {
override val label: String = PyBundle.message("python.sdk.new.project.environment")
override val steps: Map<String, NewProjectWizardStep> by lazy {
val existingSdkPanel = PyAddExistingSdkPanel(null, null, existingSdks(context), projectPath.toString(), null)
mapOf(
"New" to NewEnvironmentStep(this),
// TODO: Handle remote project creation for remote SDKs
"Existing" to PythonSdkPanelAdapterStep(this, existingSdkPanel),
)
}
override fun setupUI(builder: Panel) {
super.setupUI(builder)
step = if (PySdkSettings.instance.useNewEnvironmentForNewProject) "New" else "Existing"
}
override fun setupProject(project: Project) {
super.setupProject(project)
PySdkSettings.instance.useNewEnvironmentForNewProject = step == "New"
}
}
/**
* A new Python project wizard step that allows you to create a new Python environment of various types.
*
* The following environment types are supported:
*
* * Virtualenv
* * Conda
* * Pipenv
*
* as well as other environments offered by third-party plugins via [PySdkProvider.createNewEnvironmentPanel].
*
* The suggested new environment path for some types of Python environments depends on the path of your new project.
*/
private class NewEnvironmentStep<P>(parent: P)
: AbstractNewProjectWizardMultiStepBase(parent),
NewProjectWizardPythonData by parent
where P : NewProjectWizardStep, P : NewProjectWizardPythonData {
override val label: String = PyBundle.message("python.sdk.new.project.environment.type")
override val steps: Map<String, NewProjectWizardStep> by lazy {
val sdks = existingSdks(context)
val newProjectPath = projectPath.toString()
val basePanels = listOf(
PyAddNewVirtualEnvPanel(null, null, sdks, newProjectPath, context),
PyAddNewCondaEnvPanel(null, null, sdks, newProjectPath),
)
val providedPanels = PySdkProvider.EP_NAME.extensions()
.map { it.createNewEnvironmentPanel(null, null, sdks, newProjectPath, context) }
.toList()
val panels = basePanels + providedPanels
panels
.associateBy { it.envName }
.mapValues { (_, v) -> PythonSdkPanelAdapterStep(this, v) }
}
override fun setupUI(builder: Panel) {
super.setupUI(builder)
val preferred = PySdkSettings.instance.preferredEnvironmentType
step = if (preferred != null && preferred in steps.keys) preferred else steps.keys.first()
}
override fun setupProject(project: Project) {
super.setupProject(project)
PySdkSettings.instance.preferredEnvironmentType = step
}
}
/**
* A new Python project wizard step that allows you to get or create a Python environment via its [PyAddSdkPanel].
*/
private class PythonSdkPanelAdapterStep<P>(parent: P, val panel: PyAddSdkPanel)
: AbstractNewProjectWizardStep(parent),
NewProjectWizardPythonData by parent
where P : NewProjectWizardStep, P : NewProjectWizardPythonData {
val panelChangedProperty = propertyGraph.graphProperty {}
var panelChanged by panelChangedProperty
override fun setupUI(builder: Panel) {
with(builder) {
row {
cell(panel)
.graphProperty(panelChangedProperty)
.horizontalAlign(HorizontalAlign.FILL)
.validationOnInput { panel.validateAll().firstOrNull() }
.validationOnApply { panel.validateAll().firstOrNull() }
}
}
panel.addChangeListener {
panelChanged = Unit
pythonSdk = panel.sdk
}
nameProperty.afterChange { updateNewProjectPath() }
pathProperty.afterChange { updateNewProjectPath() }
}
override fun setupProject(project: Project) {
pythonSdk = panel.getOrCreateSdk()
}
private fun updateNewProjectPath() {
panel.newProjectPath = projectPath.toString()
}
}
/**
* Return the list of already configured Python SDKs.
*/
private fun existingSdks(context: WizardContext): List<Sdk> {
val sdksModel = ProjectSdksModel().apply {
reset(context.project)
Disposer.register(context.disposable, Disposable {
disposeUIResources()
})
}
return ProjectSpecificSettingsStep.getValidPythonSdks(sdksModel.sdks.toList())
}

View File

@@ -2,13 +2,16 @@
package com.jetbrains.python.sdk.configuration
import com.intellij.execution.ExecutionException
import com.intellij.openapi.Disposable
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.io.FileUtil
@@ -36,7 +39,13 @@ object PyProjectVirtualEnvConfiguration {
val task = object : Task.WithResult<String, ExecutionException>(project, PySdkBundle.message("python.creating.venv.title"), false) {
override fun compute(indicator: ProgressIndicator): String {
indicator.isIndeterminate = true
val packageManager = PyPackageManager.getInstance(installedSdk)
val sdk = if (installedSdk is Disposable && Disposer.isDisposed(installedSdk)) {
ProjectJdkTable.getInstance().findJdk(installedSdk.name)!!
}
else {
installedSdk
}
val packageManager = PyPackageManager.getInstance(sdk)
return packageManager.createVirtualEnv(venvRoot, inheritSitePackages)
}
}