mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[python] new ui for python interpreter creation in new project wizard (PY-60214)
- covered by registry key `python.new.interpreter.creation.ui` -- enabled by default - missing proper validation and state save (tbd) GitOrigin-RevId: 4035bb82d526be7a164715f7567d63319f4be499
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6629923a74
commit
2b96defa3b
@@ -4,6 +4,7 @@ package com.jetbrains.python.newProject.steps;
|
||||
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep;
|
||||
import com.intellij.ide.util.projectWizard.ProjectSettingsStepBase;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.platform.DirectoryProjectGenerator;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.jetbrains.python.newProject.*;
|
||||
@@ -41,6 +42,9 @@ public final class PyCharmNewProjectStep extends AbstractNewProjectStep<PyNewPro
|
||||
//noinspection unchecked
|
||||
return new NewProjectWizardProjectSettingsStep<PyNewProjectSettings>(npwGenerator);
|
||||
}
|
||||
else if (Registry.is("python.new.interpreter.creation.ui")) {
|
||||
return new PythonProjectSpecificSettingsStep<>(projectGenerator, callback);
|
||||
}
|
||||
else {
|
||||
return new ProjectSpecificSettingsStep<>(projectGenerator, callback);
|
||||
}
|
||||
|
||||
+29
@@ -15,12 +15,16 @@
|
||||
*/
|
||||
package com.jetbrains.python.newProject.steps;
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep;
|
||||
import com.intellij.ide.util.projectWizard.ProjectSettingsStepBase;
|
||||
import com.intellij.ide.util.projectWizard.WebProjectTemplate;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.platform.DirectoryProjectGenerator;
|
||||
@@ -29,15 +33,26 @@ import com.intellij.util.BooleanFunction;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.newProject.PyNewProjectSettings;
|
||||
import com.jetbrains.python.newProject.PythonProjectGenerator;
|
||||
import com.jetbrains.python.newProject.welcome.PyWelcomeSettings;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
public class PythonGenerateProjectCallback<T> extends AbstractNewProjectStep.AbstractCallback<T> {
|
||||
|
||||
@Override
|
||||
public void consume(@Nullable ProjectSettingsStepBase<T> step, @NotNull ProjectGeneratorPeer<T> projectGeneratorPeer) {
|
||||
if (!(step instanceof ProjectSpecificSettingsStep settingsStep)) return;
|
||||
|
||||
if (step instanceof PythonProjectSpecificSettingsStep) {
|
||||
// has to be set before project generation
|
||||
boolean welcomeScript = PropertiesComponent.getInstance().getBoolean("PyCharm.NewProject.Welcome", false);
|
||||
PyWelcomeSettings.getInstance().setCreateWelcomeScriptForEmptyProject(welcomeScript);
|
||||
}
|
||||
|
||||
final DirectoryProjectGenerator generator = settingsStep.getProjectGenerator();
|
||||
Sdk sdk = settingsStep.getSdk();
|
||||
|
||||
@@ -68,6 +83,20 @@ public class PythonGenerateProjectCallback<T> extends AbstractNewProjectStep.Abs
|
||||
SdkConfigurationUtil.setDirectoryProjectSdk(newProject, sdk);
|
||||
((PythonProjectGenerator<?>)generator).afterProjectGenerated(newProject);
|
||||
}
|
||||
|
||||
if (step instanceof PythonProjectSpecificSettingsStep newStep) {
|
||||
// init git repostory
|
||||
if (PropertiesComponent.getInstance().getBoolean("PyCharm.NewProject.Git", false)) {
|
||||
ModuleManager moduleManager = ModuleManager.getInstance(newProject);
|
||||
Optional<Module> module = Arrays.stream(moduleManager.getModules()).findFirst();
|
||||
module.ifPresent((value -> {
|
||||
ModuleRootManager rootManager = ModuleRootManager.getInstance(value);
|
||||
Arrays.stream(rootManager.getContentRoots()).findFirst().ifPresent(root -> {
|
||||
PythonProjectSpecificSettingsStep.initializeGit(newProject, root);
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -477,6 +477,51 @@ sdk.create.venv.conda.dialog.label.interpreter=Interpreter:
|
||||
sdk.create.venv.conda.dialog.label.location=Location:
|
||||
sdk.create.venv.conda.dialog.label.python.version=Python version:
|
||||
|
||||
|
||||
sdk.create.interpreter.type=Interpreter type:
|
||||
sdk.create.type.project.venv=Project venv
|
||||
sdk.create.type.base.conda=Base conda
|
||||
sdk.create.type.custom=Custom environment
|
||||
sdk.create.python.version=Python version:
|
||||
sdk.create.conda.executable.path=Path to conda:
|
||||
sdk.create.simple.venv.hint=Python virtual environment will be created in the project root: {0}.venv
|
||||
sdk.create.simple.conda.hint=To create a new conda environment or choose an existing one, proceed with Custom environment
|
||||
sdk.create.custom.develop.on=Develop on:
|
||||
sdk.create.custom.env.creation.type=Environment:
|
||||
sdk.create.custom.generate.new=Generate new
|
||||
sdk.create.custom.select.existing=Select existing
|
||||
sdk.create.custom.type=Type:
|
||||
sdk.create.custom.base.python=Base python:
|
||||
sdk.create.custom.location=Location:
|
||||
sdk.create.custom.inherit.packages=Inherit packages from base interpreter
|
||||
sdk.create.custom.make.available=Make available to all projects
|
||||
sdk.create.custom.python.path=Python path:
|
||||
sdk.create.custom.python.browse.tooltip="Select Python executable"
|
||||
sdk.create.custom.venv.location.browse.title=Select virtualenv location
|
||||
sdk.create.custom.conda.browse.title=Select Conda Executable
|
||||
sdk.create.custom.conda.env.name=Name:
|
||||
sdk.create.custom.conda.create.progress=Creating conda interpreter
|
||||
sdk.create.custom.conda.select.progress=Selecting conda interpreter
|
||||
sdk.create.custom.conda.refresh.envs=Reload environments
|
||||
sdk.create.custom.pipenv.path=Path to pipenv:
|
||||
sdk.create.custom.pipenv.missing.text=Pipenv executable non found. <a href='selectPipEnv'>Select executable</a>
|
||||
sdk.create.custom.poetry.path=Path to poetry:
|
||||
sdk.create.custom.poetry.missing.text=Poetry executable non found. <a href='selectPipEnv'>Select executable</a>
|
||||
|
||||
sdk.create.targets.local=Local Machine
|
||||
sdk.create.custom.virtualenv=Virtualenv
|
||||
sdk.create.custom.conda=Conda
|
||||
sdk.create.custom.pipenv=Pipenv
|
||||
sdk.create.custom.poetry=Poetry
|
||||
sdk.create.custom.python=Python
|
||||
|
||||
new.project.name=Name:
|
||||
new.project.location=Location:
|
||||
new.project.location.hint=Project will be created in: {0}
|
||||
new.project.git=Create Git repository
|
||||
new.project.welcome=Create a main.py welcome script
|
||||
|
||||
|
||||
# remote interpreters
|
||||
remote.interpreter.configure.title=Configure Remote Python Interpreter
|
||||
remote.interpreter.configure.path.title=Select Python Interpreter
|
||||
|
||||
@@ -573,6 +573,9 @@
|
||||
key="black.configurable.name"/>
|
||||
|
||||
<dependencyCollector kind="python" implementation="com.jetbrains.python.packaging.PyDependencyCollector"/>
|
||||
|
||||
<registryKey key="python.new.interpreter.creation.ui" defaultValue="true"
|
||||
description="Enable new UI for adding Python interpreter in New Project dialog." />
|
||||
</extensions>
|
||||
|
||||
<extensionPoints>
|
||||
@@ -892,6 +895,8 @@
|
||||
<!--suppress PluginXmlI18n -->
|
||||
<action id="PyUpdateProjectSdk" internal="true" class="com.jetbrains.python.sdk.PyUpdateProjectSdkAction"
|
||||
text="Update Python SDK" description="Forcibly update all configured Python SDKs in the project"/>
|
||||
<action id="PySetPropertyAction" internal="true" class="com.jetbrains.python.sdk.add.v2.PySetPropertyAction"
|
||||
text="Set PropertiesComponent Value" description="Set value in application-level PropertiesComponent"/>
|
||||
<add-to-group group-id="Internal"/>
|
||||
</group>
|
||||
</actions>
|
||||
|
||||
@@ -144,7 +144,7 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
|
||||
* @return path for project on remote side provided by user
|
||||
*/
|
||||
@Nullable
|
||||
public final String getRemotePath() {
|
||||
public String getRemotePath() {
|
||||
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
|
||||
if (interpreterPanel == null) return null;
|
||||
final PyAddExistingSdkPanel panel = ObjectUtils.tryCast(interpreterPanel.getSelectedPanel(), PyAddExistingSdkPanel.class);
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.newProject.steps
|
||||
|
||||
import com.intellij.ide.IdeBundle
|
||||
import com.intellij.ide.impl.ProjectUtil
|
||||
import com.intellij.ide.impl.ProjectUtil.getUserHomeProjectDir
|
||||
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep
|
||||
import com.intellij.openapi.GitRepositoryInitializer
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.observable.properties.PropertyGraph
|
||||
import com.intellij.openapi.observable.util.bindBooleanStorage
|
||||
import com.intellij.openapi.observable.util.joinSystemDependentPath
|
||||
import com.intellij.openapi.progress.runBackgroundableTask
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.DialogPanel
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.platform.DirectoryProjectGenerator
|
||||
import com.intellij.ui.components.JBTextField
|
||||
import com.intellij.ui.dsl.builder.*
|
||||
import com.intellij.util.PlatformUtils
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.newProject.PythonProjectGenerator
|
||||
import com.jetbrains.python.psi.PyUtil
|
||||
import com.jetbrains.python.sdk.PyLazySdk
|
||||
import com.jetbrains.python.sdk.add.v2.PythonAddNewEnvironmentPanel
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
import javax.swing.JPanel
|
||||
|
||||
class PythonProjectSpecificSettingsStep<T>(projectGenerator: DirectoryProjectGenerator<T>,
|
||||
callback: AbstractNewProjectStep.AbstractCallback<T>)
|
||||
: ProjectSpecificSettingsStep<T>(projectGenerator, callback), DumbAware {
|
||||
|
||||
private val propertyGraph = PropertyGraph()
|
||||
private val projectName = propertyGraph.property("")
|
||||
private val projectLocation = propertyGraph.property("")
|
||||
private val locationHint = propertyGraph.property("").apply {
|
||||
dependsOn(projectName, ::updateHint)
|
||||
dependsOn(projectLocation, ::updateHint)
|
||||
}
|
||||
private val createRepository = propertyGraph.property(false)
|
||||
.bindBooleanStorage("PyCharm.NewProject.Git")
|
||||
private val createScript = propertyGraph.property(false)
|
||||
.bindBooleanStorage("PyCharm.NewProject.Welcome")
|
||||
|
||||
private lateinit var projectNameFiled: JBTextField
|
||||
lateinit var mainPanel: DialogPanel
|
||||
override fun createAndFillContentPanel(): JPanel {
|
||||
return createContentPanelWithAdvancedSettingsPanel()
|
||||
}
|
||||
|
||||
override fun getProjectLocation(): String {
|
||||
return FileUtil.expandUserHome(projectLocation.joinSystemDependentPath(projectName).get())
|
||||
}
|
||||
|
||||
override fun getRemotePath(): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
private val interpreterPanel = PythonAddNewEnvironmentPanel(projectLocation.joinSystemDependentPath(projectName))
|
||||
|
||||
override fun createBasePanel(): JPanel {
|
||||
if (myProjectGenerator !is PythonProjectGenerator<*>) return super.createBasePanel()
|
||||
|
||||
myLocationField = TextFieldWithBrowseButton()
|
||||
|
||||
val nextProjectName = myProjectDirectory.get()
|
||||
projectName.set(nextProjectName.nameWithoutExtension)
|
||||
projectLocation.set(nextProjectName.parent)
|
||||
|
||||
mainPanel = panel {
|
||||
row(message("new.project.name")) {
|
||||
projectNameFiled = textField()
|
||||
.bindText(projectName)
|
||||
.component
|
||||
}
|
||||
row(message("new.project.location")) {
|
||||
textFieldWithBrowseButton()
|
||||
.bindText(projectLocation)
|
||||
.align(Align.FILL)
|
||||
}
|
||||
row("") {
|
||||
comment("", maxLineLength = 60).bindText(locationHint)
|
||||
}
|
||||
row("") {
|
||||
checkBox(message("new.project.git")).bindSelected(createRepository)
|
||||
checkBox(message("new.project.welcome")).bindSelected(createScript)
|
||||
}
|
||||
|
||||
panel {
|
||||
interpreterPanel.buildPanel(this)
|
||||
}
|
||||
}
|
||||
|
||||
mainPanel.registerValidators(this)
|
||||
myCreateButton.addActionListener { mainPanel.apply() }
|
||||
return mainPanel
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
override fun findSequentNonExistingUntitled(): File {
|
||||
return Optional
|
||||
.ofNullable(PyUtil.`as`(myProjectGenerator, PythonProjectGenerator::class.java))
|
||||
.map { it.newProjectPrefix }
|
||||
.map { FileUtil.findSequentNonexistentFile(getBaseDir(), it!!, "") }
|
||||
.orElseGet { super.findSequentNonExistingUntitled() }
|
||||
}
|
||||
|
||||
private fun getBaseDir(): File {
|
||||
if (PlatformUtils.isDataSpell() && FileUtil.isAncestor(PathManager.getConfigDir(), Paths.get(ProjectUtil.getBaseDir()), false)) {
|
||||
return File(getUserHomeProjectDir())
|
||||
}
|
||||
return File(ProjectUtil.getBaseDir())
|
||||
}
|
||||
|
||||
private fun updateHint(): String {
|
||||
return message("new.project.location.hint", Paths.get(projectLocation.get(), projectName.get()))
|
||||
}
|
||||
|
||||
override fun checkValid(): Boolean {
|
||||
// todo add proper validation with custom component
|
||||
return true
|
||||
}
|
||||
|
||||
override fun installFramework(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPanelSelected() {
|
||||
interpreterPanel.onShown()
|
||||
}
|
||||
|
||||
override fun getSdk(): Sdk {
|
||||
return PyLazySdk("Uninitialized environment") { interpreterPanel.getSdk() }
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun initializeGit(project: Project, root: VirtualFile) {
|
||||
runBackgroundableTask(IdeBundle.message("progress.title.creating.git.repository"), project) {
|
||||
GitRepositoryInitializer.getInstance()?.initRepository(project, root, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.asContextElement
|
||||
import com.intellij.openapi.observable.util.not
|
||||
import com.intellij.openapi.progress.ModalTaskOwner
|
||||
import com.intellij.openapi.progress.TaskCancellation
|
||||
import com.intellij.openapi.progress.runWithModalProgressBlocking
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton
|
||||
import com.intellij.ui.components.fields.ExtendableTextComponent
|
||||
import com.intellij.ui.dsl.builder.Align
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.ui.dsl.builder.bindItem
|
||||
import com.intellij.ui.dsl.builder.bindText
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.sdk.add.target.conda.TargetEnvironmentRequestCommandExecutor
|
||||
import com.jetbrains.python.sdk.add.target.conda.createCondaSdkFromExistingEnv
|
||||
import com.jetbrains.python.sdk.add.target.conda.suggestCondaPath
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class CondaExistingEnvironmentSelector(state: PythonAddInterpreterState) : PythonAddEnvironment(state) {
|
||||
|
||||
private lateinit var envComboBox: ComboBox<PyCondaEnv?>
|
||||
private lateinit var condaExecutableComboBox: TextFieldWithBrowseButton
|
||||
private var selectedEnvironment = propertyGraph.property<PyCondaEnv?>(null)
|
||||
private var lastLoadedConda = propertyGraph.property("")
|
||||
private var condaEnvironmentsLoaded = propertyGraph.property(true)
|
||||
|
||||
|
||||
override fun buildOptions(panel: Panel) {
|
||||
with(panel) {
|
||||
row(message("sdk.create.conda.executable.path")) {
|
||||
condaExecutableComboBox = textFieldWithBrowseButton()
|
||||
.bindText(state.condaExecutable)
|
||||
.align(Align.FILL)
|
||||
.component
|
||||
}
|
||||
|
||||
row(message("sdk.create.custom.env.creation.type")) {
|
||||
envComboBox = comboBox<PyCondaEnv?>(emptyList(), CondaEnvComboBoxListCellRenderer())
|
||||
.bindItem(selectedEnvironment)
|
||||
.enabledIf(condaEnvironmentsLoaded)
|
||||
.component
|
||||
|
||||
link(message("sdk.create.custom.conda.refresh.envs")) {
|
||||
val modalityState = ModalityState.current().asContextElement()
|
||||
state.scope.launch(Dispatchers.Default + modalityState) {
|
||||
val commandExecutor = TargetEnvironmentRequestCommandExecutor(LocalTargetEnvironmentRequest())
|
||||
val environments = PyCondaEnv.getEnvs(commandExecutor, state.condaExecutable.get())
|
||||
withContext(Dispatchers.Main + modalityState) {
|
||||
envComboBox.removeAllItems()
|
||||
val envs = environments.getOrThrow() // todo error handling
|
||||
selectedEnvironment.set(envs.first())
|
||||
envs.forEach(envComboBox::addItem)
|
||||
lastLoadedConda.set(state.condaExecutable.get())
|
||||
}
|
||||
}
|
||||
}.visibleIf(!condaEnvironmentsLoaded)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShown() {
|
||||
val modalityState = ModalityState.current().asContextElement()
|
||||
state.scope.launch(Dispatchers.Default + modalityState) {
|
||||
|
||||
val pathOnTarget = suggestCondaPath()!! // todo flow for undiscovered conda
|
||||
val commandExecutor = TargetEnvironmentRequestCommandExecutor(LocalTargetEnvironmentRequest())
|
||||
val environments = PyCondaEnv.getEnvs(commandExecutor, pathOnTarget)
|
||||
|
||||
withContext(Dispatchers.Main + modalityState) {
|
||||
|
||||
lastLoadedConda.set(pathOnTarget)
|
||||
envComboBox.removeAllItems()
|
||||
val envs = environments.getOrThrow()
|
||||
selectedEnvironment.set(envs.first())
|
||||
envs.forEach(envComboBox::addItem)
|
||||
|
||||
|
||||
val refreshIcon = ExtendableTextComponent.Extension.create(AllIcons.Actions.Refresh, AllIcons.Actions.Refresh, "Refresh conda") {
|
||||
// todo decide whether we need icon or just link
|
||||
}
|
||||
val textComponent = condaExecutableComboBox.childComponent as ExtendableTextComponent
|
||||
|
||||
condaEnvironmentsLoaded.dependsOn(state.condaExecutable, ::condasMatch)
|
||||
condaEnvironmentsLoaded.dependsOn(lastLoadedConda, ::condasMatch)
|
||||
|
||||
condaEnvironmentsLoaded.afterChange {
|
||||
if (it) textComponent.removeExtension(refreshIcon)
|
||||
else textComponent.addExtension(refreshIcon)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun condasMatch(): Boolean = state.condaExecutable.get() == lastLoadedConda.get()
|
||||
|
||||
override fun getOrCreateSdk(): Sdk {
|
||||
return runWithModalProgressBlocking(ModalTaskOwner.guess(), message("sdk.create.custom.conda.select.progress"), TaskCancellation.nonCancellable()) {
|
||||
PyCondaCommand(state.condaExecutable.get(), null)
|
||||
.createCondaSdkFromExistingEnv(selectedEnvironment.get()!!.envIdentity, state.basePythonSdks.get(),
|
||||
ProjectManager.getInstance().defaultProject)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.progress.ModalTaskOwner
|
||||
import com.intellij.openapi.progress.TaskCancellation
|
||||
import com.intellij.openapi.progress.runWithModalProgressBlocking
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.ui.dsl.builder.Align
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.ui.dsl.builder.bindItem
|
||||
import com.intellij.ui.dsl.builder.bindText
|
||||
import com.intellij.ui.dsl.listCellRenderer.textListCellRenderer
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.add.target.conda.condaSupportedLanguages
|
||||
import com.jetbrains.python.sdk.add.target.conda.createCondaSdkAlongWithNewEnv
|
||||
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import java.io.File
|
||||
|
||||
class CondaNewEnvironmentCreator(state: PythonAddInterpreterState) : PythonAddEnvironment(state) {
|
||||
|
||||
private val envName = propertyGraph.property("")
|
||||
private lateinit var pythonVersion: ObservableMutableProperty<LanguageLevel>
|
||||
private lateinit var versionComboBox: ComboBox<LanguageLevel>
|
||||
|
||||
override fun buildOptions(panel: Panel) {
|
||||
with(panel) {
|
||||
row(message("sdk.create.python.version")) {
|
||||
pythonVersion = propertyGraph.property(condaSupportedLanguages.first())
|
||||
versionComboBox = comboBox(condaSupportedLanguages, textListCellRenderer { it!!.toPythonVersion() })
|
||||
.bindItem(pythonVersion)
|
||||
.component
|
||||
}
|
||||
row(message("sdk.create.custom.conda.env.name")) {
|
||||
textField()
|
||||
.bindText(envName)
|
||||
}
|
||||
row(message("sdk.create.conda.executable.path")) {
|
||||
textFieldWithBrowseButton(message("sdk.create.custom.conda.browse.title"),
|
||||
null,
|
||||
FileChooserDescriptorFactory.createSingleFileOrExecutableAppDescriptor())
|
||||
.align(Align.FILL)
|
||||
.bindText(state.condaExecutable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShown() {
|
||||
envName.set(state.projectPath.get().substringAfterLast(File.separator))
|
||||
}
|
||||
|
||||
override fun getOrCreateSdk(): Sdk {
|
||||
return runWithModalProgressBlocking(ModalTaskOwner.guess(), message("sdk.create.custom.conda.create.progress"), TaskCancellation.nonCancellable()) {
|
||||
PyCondaCommand(state.condaExecutable.get(), null, null)
|
||||
.createCondaSdkAlongWithNewEnv(NewCondaEnvRequest.EmptyNamedEnv(pythonVersion.get(), envName.get()),
|
||||
Dispatchers.EDT,
|
||||
state.basePythonSdks.get(),
|
||||
ProjectManager.getInstance().defaultProject).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.asContextElement
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.util.text.nullize
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.sdk.pipenv.detectPipEnvExecutable
|
||||
import com.jetbrains.python.sdk.pipenv.pipEnvPath
|
||||
import com.jetbrains.python.sdk.pipenv.setupPipEnvSdkUnderProgress
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class PipEnvNewEnvironmentCreator(state: PythonAddInterpreterState) : PythonAddEnvironment(state) {
|
||||
private val executable = propertyGraph.property(UNKNOWN_EXECUTABLE)
|
||||
private lateinit var pipEnvPathField: TextFieldWithBrowseButton
|
||||
private lateinit var basePythonComboBox: ComboBox<String>
|
||||
|
||||
override fun buildOptions(panel: Panel) {
|
||||
with(panel) {
|
||||
row(message("sdk.create.custom.base.python")) {
|
||||
basePythonComboBox = pythonBaseInterpreterComboBox(state.basePythonHomePaths, state.basePythonHomePath)
|
||||
}
|
||||
|
||||
pipEnvPathField = executableSelector(message("sdk.create.custom.pipenv.path"), executable,
|
||||
message("sdk.create.custom.pipenv.missing.text"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onShown() {
|
||||
val savedPath = PropertiesComponent.getInstance().pipEnvPath
|
||||
if (savedPath != null) {
|
||||
executable.set(savedPath)
|
||||
}
|
||||
else {
|
||||
val modalityState = ModalityState.current().asContextElement()
|
||||
state.scope.launch(Dispatchers.Default + modalityState) {
|
||||
val detectedExecutable = detectPipEnvExecutable()
|
||||
withContext(Dispatchers.Main + modalityState) {
|
||||
detectedExecutable?.let { executable.set(it.path) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOrCreateSdk(): Sdk {
|
||||
PropertiesComponent.getInstance().pipEnvPath = pipEnvPathField.text.nullize()
|
||||
return setupPipEnvSdkUnderProgress(null, null, state.basePythonSdks.get(), state.projectPath.get(),
|
||||
state.basePythonVersion.get()!!.homePath, false)!!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.asContextElement
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.util.text.nullize
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.sdk.poetry.detectPoetryExecutable
|
||||
import com.jetbrains.python.sdk.poetry.poetryPath
|
||||
import com.jetbrains.python.sdk.poetry.setupPoetrySdkUnderProgress
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class PoetryNewEnvironmentCreator(state: PythonAddInterpreterState) : PythonAddEnvironment(state) {
|
||||
|
||||
val executable = propertyGraph.property(UNKNOWN_EXECUTABLE)
|
||||
private lateinit var poetryPathField: TextFieldWithBrowseButton
|
||||
private lateinit var basePythonComboBox: ComboBox<String>
|
||||
override fun buildOptions(panel: Panel) {
|
||||
with(panel) {
|
||||
row(message("sdk.create.custom.base.python")) {
|
||||
basePythonComboBox = pythonBaseInterpreterComboBox(state.basePythonHomePaths, state.basePythonHomePath)
|
||||
}
|
||||
|
||||
poetryPathField = executableSelector(message("sdk.create.custom.poetry.path"), executable,
|
||||
message("sdk.create.custom.poetry.missing.text"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShown() {
|
||||
val savedPath = PropertiesComponent.getInstance().poetryPath
|
||||
if (savedPath != null) {
|
||||
executable.set(savedPath)
|
||||
}
|
||||
else {
|
||||
val modalityState = ModalityState.current().asContextElement()
|
||||
state.scope.launch(Dispatchers.Default + modalityState) {
|
||||
val poetryExecutable = detectPoetryExecutable()
|
||||
withContext(Dispatchers.Main + modalityState) {
|
||||
poetryExecutable?.let { executable.set(it.path) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun getOrCreateSdk(): Sdk {
|
||||
PropertiesComponent.getInstance().poetryPath = executable.get().nullize()
|
||||
return setupPoetrySdkUnderProgress(null, null, state.basePythonSdks.get(), state.projectPath.get(),
|
||||
state.basePythonVersion.get()!!.homePath, false)!!
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.observable.properties.AtomicProperty
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.ui.components.dialog
|
||||
import com.intellij.ui.dsl.builder.Align
|
||||
import com.intellij.ui.dsl.builder.bindText
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
|
||||
class PySetPropertyAction : DumbAwareAction() {
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val key = AtomicProperty("")
|
||||
val value = AtomicProperty("")
|
||||
val panel = panel {
|
||||
row("Key:") {
|
||||
textField().bindText(key).align(Align.FILL)
|
||||
}
|
||||
row("Value:") {
|
||||
textField().bindText(value).align(Align.FILL)
|
||||
}
|
||||
}
|
||||
|
||||
val result = dialog("Set value in PropertiesComponent", panel, false).showAndGet()
|
||||
if (result) {
|
||||
PropertiesComponent.getInstance().setValue(key.get(), value.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.jetbrains.python.sdk.add.v2.PythonInterpreterCreationTargets.LOCAL_MACHINE
|
||||
|
||||
class PythonAddCustomInterpreter(private val settings: PythonAddInterpreterState) {
|
||||
|
||||
private lateinit var targetSelector: ComboBox<PythonInterpreterCreationTargets>
|
||||
|
||||
private val targets = mapOf(
|
||||
LOCAL_MACHINE to PythonLocalEnvironmentCreator(settings),
|
||||
)
|
||||
|
||||
fun buildPanel(outerPanel: Panel) {
|
||||
with(outerPanel) {
|
||||
|
||||
// todo uncomment for all available targets
|
||||
//row(message("sdk.create.custom.develop.on")) {
|
||||
// targetSelector = comboBox(targets.keys, PythonEnvironmentComboBoxRenderer())
|
||||
// .widthGroup("env_aligned")
|
||||
// .component
|
||||
//}
|
||||
//targets.forEach { target ->
|
||||
// rowsRange {
|
||||
// target.value.buildPanel(this)
|
||||
// }.visibleIf(targetSelector.selectedValueMatches { it == target.key })
|
||||
//}
|
||||
|
||||
|
||||
rowsRange {
|
||||
targets[LOCAL_MACHINE]!!.buildPanel(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onShown() {
|
||||
targets.values.forEach(PythonLocalEnvironmentCreator::onShown)
|
||||
}
|
||||
|
||||
fun getSdk(): Sdk {
|
||||
return targets[targetSelector.selectedItem]!!.getSdk()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.observable.properties.ObservableProperty
|
||||
import com.intellij.openapi.observable.properties.PropertyGraph
|
||||
import com.intellij.openapi.observable.util.transform
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.sdk.add.target.createDetectedSdk
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
class PythonAddInterpreterState(
|
||||
val propertyGraph: PropertyGraph,
|
||||
val projectPath: ObservableProperty<String>,
|
||||
val scope: CoroutineScope,
|
||||
val basePythonSdks: ObservableMutableProperty<List<Sdk>>,
|
||||
val allExistingSdks: ObservableMutableProperty<List<Sdk>>,
|
||||
val basePythonVersion: ObservableMutableProperty<Sdk?>,
|
||||
val condaExecutable: ObservableMutableProperty<String>,
|
||||
) {
|
||||
val basePythonHomePath = basePythonVersion.transform({ sdk -> sdk?.homePath ?: "" },
|
||||
{ path -> basePythonSdks.get().find { it.homePath == path }!! })
|
||||
|
||||
val basePythonHomePaths = transformSdksToHomePath(basePythonSdks)
|
||||
val allValidSdkPaths = transformSdksToHomePath(allExistingSdks)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private fun transformSdksToHomePath(sdkList: ObservableMutableProperty<List<Sdk>>): ObservableMutableProperty<List<String>> {
|
||||
return sdkList.transform({ sdks -> sdks.map { it.homePath!! } },
|
||||
{ paths ->
|
||||
val existing = sdkList.get()
|
||||
paths.map { path ->
|
||||
existing.find { it.homePath == path } ?: createDetectedSdk(path, isLocal = true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.asContextElement
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.observable.properties.ObservableProperty
|
||||
import com.intellij.openapi.observable.properties.PropertyGraph
|
||||
import com.intellij.openapi.observable.util.or
|
||||
import com.intellij.openapi.progress.ModalTaskOwner
|
||||
import com.intellij.openapi.progress.TaskCancellation
|
||||
import com.intellij.openapi.progress.runWithModalProgressBlocking
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.ui.dsl.builder.*
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
|
||||
import com.jetbrains.python.newProject.steps.ProjectSpecificSettingsStep
|
||||
import com.jetbrains.python.sdk.add.target.conda.TargetEnvironmentRequestCommandExecutor
|
||||
import com.jetbrains.python.sdk.add.target.conda.createCondaSdkFromExistingEnv
|
||||
import com.jetbrains.python.sdk.add.target.conda.suggestCondaPath
|
||||
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMode.*
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectVirtualEnvConfiguration
|
||||
import com.jetbrains.python.sdk.findBaseSdks
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.exists
|
||||
|
||||
class PythonAddNewEnvironmentPanel(val projectPath: ObservableProperty<String>) {
|
||||
private val propertyGraph = PropertyGraph()
|
||||
|
||||
private var selectedMode = propertyGraph.property(PROJECT_VENV)
|
||||
private var _projectVenv = propertyGraph.booleanProperty(selectedMode, PROJECT_VENV)
|
||||
private var _baseConda = propertyGraph.booleanProperty(selectedMode, BASE_CONDA)
|
||||
private var _custom = propertyGraph.booleanProperty(selectedMode, CUSTOM)
|
||||
|
||||
private val allExistingSdks = propertyGraph.property<List<Sdk>>(emptyList())
|
||||
private val basePythonSdks = propertyGraph.property<List<Sdk>>(emptyList())
|
||||
private val pythonBaseVersion = propertyGraph.property<Sdk?>(null)
|
||||
|
||||
private val condaExecutable = propertyGraph.property("")
|
||||
private var venvHint = propertyGraph.property("")
|
||||
|
||||
private lateinit var pythonBaseVersionComboBox: ComboBox<Sdk?>
|
||||
private lateinit var baseCondaEnv: PyCondaEnv
|
||||
|
||||
private var initialized = false
|
||||
|
||||
private fun updateVenvLocationHint() {
|
||||
if (selectedMode.get() == PROJECT_VENV) venvHint.set(message("sdk.create.simple.venv.hint", projectPath.get() + File.separator))
|
||||
else if (selectedMode.get() == BASE_CONDA) venvHint.set(message("sdk.create.simple.conda.hint"))
|
||||
}
|
||||
|
||||
val state = PythonAddInterpreterState(propertyGraph,
|
||||
projectPath,
|
||||
service<PythonAddSdkService>().coroutineScope,
|
||||
basePythonSdks,
|
||||
allExistingSdks,
|
||||
pythonBaseVersion,
|
||||
condaExecutable)
|
||||
|
||||
private val custom = PythonAddCustomInterpreter(state)
|
||||
|
||||
|
||||
fun buildPanel(outerPanel: Panel) {
|
||||
with(outerPanel) {
|
||||
row(message("sdk.create.interpreter.type")) {
|
||||
segmentedButton(PythonInterpreterSelectionMode.entries) { text = message(it.nameKey) }
|
||||
.bind(selectedMode)
|
||||
}.topGap(TopGap.SMALL)
|
||||
|
||||
|
||||
row(message("sdk.create.python.version")) {
|
||||
pythonBaseVersionComboBox = comboBox<Sdk?>(emptyList(), PythonSdkComboBoxListCellRenderer())
|
||||
.bindItem(pythonBaseVersion)
|
||||
.align(AlignX.FILL)
|
||||
.component
|
||||
}.visibleIf(_projectVenv)
|
||||
|
||||
row(message("sdk.create.conda.executable.path")) {
|
||||
textFieldWithBrowseButton()
|
||||
.bindText(condaExecutable)
|
||||
.validationOnInput {
|
||||
if (!Paths.get(it.text).exists()) error("Executable does not exist") else null
|
||||
}
|
||||
.align(AlignX.FILL)
|
||||
}.visibleIf(_baseConda)
|
||||
|
||||
row("") {
|
||||
comment("").bindText(venvHint)
|
||||
}.visibleIf(_projectVenv or _baseConda)
|
||||
|
||||
rowsRange {
|
||||
custom.buildPanel(this)
|
||||
}.visibleIf(_custom)
|
||||
}
|
||||
|
||||
basePythonSdks.afterChange {
|
||||
pythonBaseVersionComboBox.removeAllItems()
|
||||
it.forEach { pythonBaseVersionComboBox.addItem(it) }
|
||||
}
|
||||
|
||||
projectPath.afterChange { updateVenvLocationHint() }
|
||||
selectedMode.afterChange { updateVenvLocationHint() }
|
||||
|
||||
// todo why doesn't work?
|
||||
//venvHint.dependsOn(projectPath, ::updateVenvLocationHint)
|
||||
//venvHint.dependsOn(selectedMode, ::updateVenvLocationHint)
|
||||
}
|
||||
|
||||
fun onShown() {
|
||||
if (!initialized) {
|
||||
initialized = true
|
||||
val modalityState = ModalityState.current().asContextElement()
|
||||
state.scope.launch(Dispatchers.Default + modalityState) {
|
||||
val existingSdks = PyConfigurableInterpreterList.getInstance(null).getModel().sdks.toList()
|
||||
val baseSdks = findBaseSdks(existingSdks, null, UserDataHolderBase())
|
||||
val allValidSdks = ProjectSpecificSettingsStep.getValidPythonSdks(existingSdks)
|
||||
val validBaseSdks = ProjectSpecificSettingsStep.getValidPythonSdks(baseSdks)
|
||||
|
||||
withContext(Dispatchers.Main + modalityState) {
|
||||
pythonBaseVersionComboBox.removeAllItems()
|
||||
validBaseSdks.forEach { pythonBaseVersionComboBox.addItem(it) }
|
||||
basePythonSdks.set(validBaseSdks)
|
||||
allExistingSdks.set(allValidSdks)
|
||||
|
||||
updateVenvLocationHint()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// todo maybe do this when env requested
|
||||
state.scope.launch(Dispatchers.Default + modalityState) {
|
||||
val condaPath = suggestCondaPath() ?: return@launch // todo make conda a executableSelector component
|
||||
val commandExecutor = TargetEnvironmentRequestCommandExecutor(LocalTargetEnvironmentRequest())
|
||||
val environments = PyCondaEnv.getEnvs(commandExecutor, condaPath)
|
||||
val baseConda = environments.getOrThrow().find { env -> env.envIdentity.let { it is PyCondaEnvIdentity.UnnamedEnv && it.isBase } }
|
||||
withContext(Dispatchers.Main + modalityState) {
|
||||
condaExecutable.set(condaPath)
|
||||
baseCondaEnv = baseConda!!
|
||||
}
|
||||
}
|
||||
|
||||
custom.onShown()
|
||||
}
|
||||
}
|
||||
|
||||
//fun validateCurrent(): Boolean {
|
||||
// return when (selectedMode.get()) {
|
||||
// PROJECT_VENV -> pythonBaseVersion.get().let { it != null && it.sdkSeemsValid }
|
||||
// BASE_CONDA -> {
|
||||
// val condaPath = condaExecutable.get()
|
||||
// return condaPath != null
|
||||
// }
|
||||
// CUSTOM -> error("")
|
||||
// }
|
||||
//}
|
||||
|
||||
fun getSdk(): Sdk? {
|
||||
return when (selectedMode.get()) {
|
||||
PROJECT_VENV -> {
|
||||
val venvPath = projectPath.get() + File.separator + ".venv"
|
||||
return PyProjectVirtualEnvConfiguration.createVirtualEnvSynchronously(pythonBaseVersion.get(), basePythonSdks.get(), venvPath,
|
||||
projectPath.get(),
|
||||
null, null)
|
||||
}
|
||||
BASE_CONDA -> {
|
||||
runWithModalProgressBlocking(ModalTaskOwner.guess(), message("sdk.create.custom.conda.select.progress"), TaskCancellation.nonCancellable()) {
|
||||
PyCondaCommand(condaExecutable.get(), null)
|
||||
.createCondaSdkFromExistingEnv(baseCondaEnv.envIdentity, basePythonSdks.get(), ProjectManager.getInstance().defaultProject)
|
||||
}
|
||||
}
|
||||
CUSTOM -> custom.getSdk()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
|
||||
class PythonExistingEnvironmentSelector(state: PythonAddInterpreterState) : PythonAddEnvironment(state) {
|
||||
private lateinit var versionComboBox: ComboBox<String>
|
||||
private val selectedPath = propertyGraph.property("")
|
||||
|
||||
override fun buildOptions(panel: Panel) {
|
||||
with(panel) {
|
||||
row(message("sdk.create.custom.python.path")) {
|
||||
versionComboBox = pythonBaseInterpreterComboBox(state.allValidSdkPaths, selectedPath)
|
||||
}
|
||||
}
|
||||
selectedPath.dependsOn(state.basePythonHomePath) {
|
||||
state.basePythonHomePath.get()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOrCreateSdk(): Sdk {
|
||||
return state.allExistingSdks.get().find { it.homePath == selectedPath.get() }!!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.openapi.observable.util.and
|
||||
import com.intellij.openapi.observable.util.equalsTo
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.ui.dsl.builder.bind
|
||||
import com.intellij.ui.dsl.builder.bindItem
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.sdk.add.v2.PythonSupportedEnvironmentManagers.*
|
||||
|
||||
class PythonLocalEnvironmentCreator(private val settings: PythonAddInterpreterState) : PythonTargetEnvironmentInterpreterCreator {
|
||||
|
||||
private val selectionMethod = settings.propertyGraph.property(PythonInterpreterSelectionMethod.CREATE_NEW)
|
||||
|
||||
private val _createNew = settings.propertyGraph.booleanProperty(selectionMethod, PythonInterpreterSelectionMethod.CREATE_NEW)
|
||||
private val _selectExisting = settings.propertyGraph.booleanProperty(selectionMethod, PythonInterpreterSelectionMethod.SELECT_EXISTING)
|
||||
|
||||
private val newInterpreterManager = settings.propertyGraph.property(VIRTUALENV)
|
||||
private val existingInterpreterManager = settings.propertyGraph.property(PYTHON)
|
||||
|
||||
private val newInterpreterCreators = mapOf(
|
||||
VIRTUALENV to PythonNewVirtualenvCreator(settings),
|
||||
CONDA to CondaNewEnvironmentCreator(settings),
|
||||
PIPENV to PipEnvNewEnvironmentCreator(settings),
|
||||
POETRY to PoetryNewEnvironmentCreator(settings),
|
||||
)
|
||||
|
||||
private val existingInterpreterSelectors = mapOf(
|
||||
PYTHON to PythonExistingEnvironmentSelector(settings),
|
||||
CONDA to CondaExistingEnvironmentSelector(settings),
|
||||
)
|
||||
|
||||
|
||||
override fun buildPanel(outerPanel: Panel) {
|
||||
with(outerPanel) {
|
||||
buttonsGroup {
|
||||
row(message("sdk.create.custom.env.creation.type")) {
|
||||
radioButton(message("sdk.create.custom.generate.new"), PythonInterpreterSelectionMethod.CREATE_NEW).onChanged {
|
||||
selectionMethod.set(if (it.isSelected) PythonInterpreterSelectionMethod.CREATE_NEW else PythonInterpreterSelectionMethod.SELECT_EXISTING)
|
||||
}
|
||||
radioButton(message("sdk.create.custom.select.existing"), PythonInterpreterSelectionMethod.SELECT_EXISTING).onChanged {
|
||||
selectionMethod.set(if (it.isSelected) PythonInterpreterSelectionMethod.SELECT_EXISTING else PythonInterpreterSelectionMethod.CREATE_NEW)
|
||||
}
|
||||
}
|
||||
}.bind({ selectionMethod.get() }, { selectionMethod.set(it) } )
|
||||
|
||||
row(message("sdk.create.custom.type")) {
|
||||
comboBox(newInterpreterCreators.keys, PythonEnvironmentComboBoxRenderer())
|
||||
.bindItem(newInterpreterManager)
|
||||
.widthGroup("env_aligned")
|
||||
.visibleIf(_createNew)
|
||||
|
||||
comboBox(existingInterpreterSelectors.keys, PythonEnvironmentComboBoxRenderer())
|
||||
.bindItem(existingInterpreterManager)
|
||||
.widthGroup("env_aligned")
|
||||
.visibleIf(_selectExisting)
|
||||
}
|
||||
|
||||
newInterpreterCreators.forEach { (type, creator) ->
|
||||
rowsRange {
|
||||
creator.buildOptions(this)
|
||||
}.visibleIf(_createNew and newInterpreterManager.equalsTo(type))
|
||||
}
|
||||
|
||||
existingInterpreterSelectors.forEach { (type, selector) ->
|
||||
rowsRange {
|
||||
selector.buildOptions(this)
|
||||
}.visibleIf(_selectExisting and existingInterpreterManager.equalsTo(type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onShown() {
|
||||
newInterpreterCreators.values.forEach(PythonAddEnvironment::onShown)
|
||||
existingInterpreterSelectors.values.forEach(PythonAddEnvironment::onShown)
|
||||
}
|
||||
|
||||
override fun getSdk(): Sdk {
|
||||
val sdkManager =
|
||||
if (_selectExisting.get()) existingInterpreterSelectors[existingInterpreterManager.get()]
|
||||
else newInterpreterCreators[newInterpreterManager.get()]
|
||||
return sdkManager!!.getOrCreateSdk()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.asContextElement
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.ui.dsl.builder.*
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.sdk.PySdkSettings
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectVirtualEnvConfiguration
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.exists
|
||||
|
||||
class PythonNewVirtualenvCreator(state: PythonAddInterpreterState) : PythonAddEnvironment(state) {
|
||||
private val location = propertyGraph.property("")
|
||||
private val inheritSitePackages = propertyGraph.property(false)
|
||||
private val makeAvailable = propertyGraph.property(false)
|
||||
private lateinit var versionComboBox: ComboBox<String>
|
||||
private var locationModified = false
|
||||
|
||||
override fun buildOptions(panel: Panel) {
|
||||
with(panel) {
|
||||
row(message("sdk.create.custom.base.python")) {
|
||||
versionComboBox = pythonBaseInterpreterComboBox(state.basePythonHomePaths,
|
||||
state.basePythonHomePath)
|
||||
|
||||
}
|
||||
row(message("sdk.create.custom.location")) {
|
||||
textFieldWithBrowseButton(message("sdk.create.custom.venv.location.browse.title"), fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor())
|
||||
.bindText(location)
|
||||
.whenTextChangedFromUi { locationModified = true }
|
||||
.cellValidation {
|
||||
addInputRule("This directory already exists") {
|
||||
Paths.get(it.text).exists() // todo validation with custom component, once available
|
||||
}
|
||||
}
|
||||
.align(Align.FILL)
|
||||
}
|
||||
|
||||
row("") {
|
||||
checkBox(message("sdk.create.custom.inherit.packages"))
|
||||
.bindSelected(inheritSitePackages)
|
||||
}
|
||||
row("") {
|
||||
checkBox(message("sdk.create.custom.make.available"))
|
||||
.bindSelected(makeAvailable)
|
||||
}
|
||||
}
|
||||
|
||||
state.projectPath.afterChange {
|
||||
if (!locationModified) {
|
||||
location.set(PySdkSettings.instance.getPreferredVirtualEnvBasePath(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShown() {
|
||||
val modalityState = ModalityState.current().asContextElement()
|
||||
state.scope.launch(Dispatchers.Default + modalityState) {
|
||||
val basePath = PySdkSettings.instance.getPreferredVirtualEnvBasePath(state.projectPath.get())
|
||||
withContext(Dispatchers.Main + modalityState) {
|
||||
location.set(basePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOrCreateSdk(): Sdk {
|
||||
return PyProjectVirtualEnvConfiguration.createVirtualEnvSynchronously(state.basePythonVersion.get(),
|
||||
state.basePythonSdks.get(),
|
||||
location.get(),
|
||||
state.projectPath.get(),
|
||||
null, null,
|
||||
inheritSitePackages = inheritSitePackages.get(),
|
||||
makeShared = makeAvailable.get())!!
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.jetbrains.python.sdk.pipenv.PIPENV_ICON
|
||||
import com.jetbrains.python.sdk.poetry.POETRY_ICON
|
||||
import icons.PythonIcons
|
||||
import icons.PythonSdkIcons
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import javax.swing.Icon
|
||||
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
class PythonAddSdkService(val coroutineScope: CoroutineScope)
|
||||
|
||||
interface PythonTargetEnvironmentInterpreterCreator {
|
||||
fun buildPanel(outerPanel: Panel)
|
||||
fun onShown() {}
|
||||
fun getSdk(): Sdk
|
||||
}
|
||||
|
||||
abstract class PythonAddEnvironment(val state: PythonAddInterpreterState) {
|
||||
|
||||
internal val propertyGraph
|
||||
get() = state.propertyGraph
|
||||
|
||||
abstract fun buildOptions(panel: Panel)
|
||||
open fun onShown() {}
|
||||
abstract fun getOrCreateSdk(): Sdk
|
||||
}
|
||||
|
||||
enum class PythonSupportedEnvironmentManagers(val nameKey: String, val icon: Icon) {
|
||||
VIRTUALENV("sdk.create.custom.virtualenv", PythonIcons.Python.Virtualenv),
|
||||
CONDA("sdk.create.custom.conda", PythonIcons.Python.Anaconda),
|
||||
POETRY("sdk.create.custom.poetry", POETRY_ICON),
|
||||
PIPENV("sdk.create.custom.pipenv", PIPENV_ICON),
|
||||
PYTHON("sdk.create.custom.python", PythonSdkIcons.Python)
|
||||
}
|
||||
|
||||
enum class PythonInterpreterSelectionMode(val nameKey: String) {
|
||||
PROJECT_VENV("sdk.create.type.project.venv"),
|
||||
BASE_CONDA("sdk.create.type.base.conda"),
|
||||
CUSTOM("sdk.create.type.custom")
|
||||
}
|
||||
|
||||
enum class PythonInterpreterCreationTargets(val nameKey: String, val icon: Icon) {
|
||||
LOCAL_MACHINE("sdk.create.targets.local", AllIcons.Nodes.HomeFolder),
|
||||
SSH("", AllIcons.Nodes.HomeFolder),
|
||||
DOCKER("", AllIcons.Nodes.HomeFolder)
|
||||
}
|
||||
|
||||
enum class PythonInterpreterSelectionMethod {
|
||||
CREATE_NEW, SELECT_EXISTING
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.sdk.add.v2
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.fileChooser.FileChooser
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.observable.properties.ObservableProperty
|
||||
import com.intellij.openapi.observable.properties.PropertyGraph
|
||||
import com.intellij.openapi.observable.util.*
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton
|
||||
import com.intellij.ui.ColoredListCellRenderer
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.components.fields.ExtendableTextComponent
|
||||
import com.intellij.ui.components.fields.ExtendableTextField
|
||||
import com.intellij.ui.dsl.builder.*
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.sdk.PyDetectedSdk
|
||||
import com.jetbrains.python.sdk.flavors.conda.*
|
||||
import org.jetbrains.annotations.Nls
|
||||
import javax.swing.JList
|
||||
import javax.swing.JTextField
|
||||
import javax.swing.plaf.basic.BasicComboBoxEditor
|
||||
|
||||
|
||||
internal fun <T> PropertyGraph.booleanProperty(dependency: ObservableProperty<T>, value: T) =
|
||||
lazyProperty { false }.apply { dependsOn(dependency) { dependency.get() == value } }
|
||||
|
||||
|
||||
class PythonSdkComboBoxListCellRenderer : ColoredListCellRenderer<Any>() {
|
||||
override fun customizeCellRenderer(list: JList<out Any>, value: Any?, index: Int, selected: Boolean, hasFocus: Boolean) {
|
||||
when (value) {
|
||||
is PyDetectedSdk -> append(value.homePath!!)
|
||||
is Sdk -> {
|
||||
append(value.versionString!!)
|
||||
append(" " + value.homePath!!, SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES)
|
||||
}
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
}
|
||||
class CondaEnvComboBoxListCellRenderer : ColoredListCellRenderer<PyCondaEnv>() {
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
override fun customizeCellRenderer(list: JList<out PyCondaEnv>, value: PyCondaEnv?, index: Int, selected: Boolean, hasFocus: Boolean) {
|
||||
when (val identity = value?.envIdentity) {
|
||||
is PyCondaEnvIdentity.NamedEnv -> append(identity.userReadableName)
|
||||
is PyCondaEnvIdentity.UnnamedEnv -> append(identity.envPath)
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PythonEnvironmentComboBoxRenderer : ColoredListCellRenderer<Any>() {
|
||||
override fun customizeCellRenderer(list: JList<out Any>, value: Any, index: Int, selected: Boolean, hasFocus: Boolean) {
|
||||
when (value) {
|
||||
is PythonSupportedEnvironmentManagers, -> {
|
||||
icon = value.icon
|
||||
append(message(value.nameKey))
|
||||
}
|
||||
is PythonInterpreterCreationTargets -> {
|
||||
icon = value.icon
|
||||
append(message(value.nameKey))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun Row.pythonBaseInterpreterComboBox(sdkHomePaths: ObservableMutableProperty<List<String>>,
|
||||
selectedPath: ObservableMutableProperty<String>): ComboBox<String> {
|
||||
val component = comboBox<String>(emptyList())
|
||||
.bindItem(selectedPath)
|
||||
.align(Align.FILL)
|
||||
.component
|
||||
|
||||
val browseExtension = ExtendableTextComponent.Extension.create(AllIcons.General.OpenDisk,
|
||||
AllIcons.General.OpenDiskHover,
|
||||
message("sdk.create.custom.python.browse.tooltip")) {
|
||||
FileChooser.chooseFile(FileChooserDescriptorFactory.createSingleFileOrExecutableAppDescriptor(), null, null) { file ->
|
||||
val path = file?.path ?: return@chooseFile
|
||||
val homePaths = sdkHomePaths.get()
|
||||
if (!homePaths.contains(path)) {
|
||||
sdkHomePaths.set(listOf(path) + homePaths)
|
||||
}
|
||||
selectedPath.set(path)
|
||||
}
|
||||
}
|
||||
|
||||
component.isEditable = true
|
||||
component.editor = object : BasicComboBoxEditor(){
|
||||
override fun createEditorComponent(): JTextField {
|
||||
val field = ExtendableTextField()
|
||||
field.addExtension(browseExtension)
|
||||
field.setBorder(null)
|
||||
field.isEditable = false
|
||||
field.background = JBUI.CurrentTheme.Arrow.backgroundColor(true, true)
|
||||
return field
|
||||
}
|
||||
}
|
||||
|
||||
sdkHomePaths.afterChange {
|
||||
component.removeAllItems()
|
||||
it.forEach(component::addItem)
|
||||
}
|
||||
|
||||
return component
|
||||
}
|
||||
|
||||
|
||||
const val UNKNOWN_EXECUTABLE = "<unknown_executable>"
|
||||
|
||||
fun Panel.executableSelector(labelText: @Nls String, executable: ObservableMutableProperty<String>, missingExecutableText: @Nls String): TextFieldWithBrowseButton {
|
||||
var textFieldComponent: TextFieldWithBrowseButton? = null
|
||||
row("") {
|
||||
icon(AllIcons.General.Warning)
|
||||
text(missingExecutableText) {
|
||||
FileChooser.chooseFile(FileChooserDescriptorFactory.createSingleFileOrExecutableAppDescriptor(), null, null) {
|
||||
executable.set(it.path)
|
||||
}
|
||||
}
|
||||
}.visibleIf(executable.equalsTo(UNKNOWN_EXECUTABLE))
|
||||
|
||||
row(labelText) {
|
||||
textFieldComponent = textFieldWithBrowseButton()
|
||||
.bindText(executable)
|
||||
.align(AlignX.FILL)
|
||||
.component
|
||||
}.visibleIf(executable.notEqualsTo(UNKNOWN_EXECUTABLE))
|
||||
|
||||
return textFieldComponent!!
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user