(), PythonPromoProjectGenerator {
+internal class JavaScriptPromoProjectGenerator : PromoProjectGenerator(isPython = false) {
override fun getName(): String {
return JAVASCRIPT_NAME
}
@@ -30,9 +25,6 @@ internal class JavaScriptPromoProjectGenerator : DirectoryProjectGeneratorBase
+ class="com.intellij.pycharm.community.ide.impl.newProject.impl.PyV3NewProjectStepAction"/>
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/PyCharmNewProjectAction.java b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/PyCharmNewProjectAction.java
index 54cd36cb4da9..7d13802fd76f 100644
--- a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/PyCharmNewProjectAction.java
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/PyCharmNewProjectAction.java
@@ -5,7 +5,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.DumbAwareAction;
import org.jetbrains.annotations.NotNull;
-public class PyCharmNewProjectAction extends DumbAwareAction {
+public final class PyCharmNewProjectAction extends DumbAwareAction {
@Override
public void actionPerformed(@NotNull final AnActionEvent e) {
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/PyCharmNewProjectDialog.java b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/PyCharmNewProjectDialog.java
index 9173b95f402e..d1f712d000ad 100644
--- a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/PyCharmNewProjectDialog.java
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/PyCharmNewProjectDialog.java
@@ -3,13 +3,13 @@ package com.intellij.pycharm.community.ide.impl.newProject;
import com.intellij.ide.util.projectWizard.AbstractNewProjectDialog;
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep;
-import com.intellij.pycharm.community.ide.impl.newProject.steps.PyCharmNewProjectStep;
+import com.intellij.pycharm.community.ide.impl.newProject.impl.PyV3NewProjectStepAction;
import org.jetbrains.annotations.NotNull;
-public class PyCharmNewProjectDialog extends AbstractNewProjectDialog {
+public final class PyCharmNewProjectDialog extends AbstractNewProjectDialog {
@Override
protected @NotNull AbstractNewProjectStep> createNewProjectStep() {
- return new PyCharmNewProjectStep();
+ return new PyV3NewProjectStepAction();
}
@Override
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/PyV3NewProjectStepAction.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/PyV3NewProjectStepAction.kt
new file mode 100644
index 000000000000..6762b6f141e0
--- /dev/null
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/PyV3NewProjectStepAction.kt
@@ -0,0 +1,66 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.pycharm.community.ide.impl.newProject.impl
+
+import com.intellij.ide.util.projectWizard.AbstractNewProjectStep
+import com.intellij.ide.util.projectWizard.ProjectSettingsStepBase
+import com.intellij.openapi.actionSystem.AnAction
+import com.intellij.openapi.actionSystem.DefaultActionGroup
+import com.intellij.openapi.wm.impl.welcomeScreen.collapsedActionGroup.CollapsedActionGroup
+import com.intellij.platform.DirectoryProjectGenerator
+import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
+import com.intellij.pycharm.community.ide.impl.newProject.impl.emptyProject.PyV3EmptyProjectGenerator
+import com.intellij.pycharm.community.ide.impl.newProject.steps.PythonGenerateProjectCallback
+import com.jetbrains.python.newProject.PyNewProjectSettings
+import com.jetbrains.python.newProject.PythonProjectGenerator
+import com.jetbrains.python.newProject.promotion.PromoProjectGenerator
+import com.jetbrains.python.newProject.promotion.PromoStep
+import com.jetbrains.python.newProject.steps.PythonProjectSpecificSettingsStep
+import com.jetbrains.python.newProjectWizard.PyV3BaseProjectSettings
+import com.jetbrains.python.newProjectWizard.PyV3ProjectBaseGenerator
+
+internal class PyV3NewProjectStepAction : AbstractNewProjectStep(PyV3Customization) {
+
+ private object PyV3Customization : Customization() {
+ override fun createEmptyProjectGenerator(): DirectoryProjectGenerator = PyV3EmptyProjectGenerator()
+
+ override fun createProjectSpecificSettingsStep(
+ projectGenerator: DirectoryProjectGenerator,
+ callback: AbstractCallback,
+ ): ProjectSettingsStepBase =
+ when (projectGenerator) {
+ // Python projects with project path, python SDK and other settings
+ is PyV3ProjectBaseGenerator<*> -> PyV3ProjectSpecificStep(projectGenerator, callback)
+ // No "create" button, no any other setting: just promotion
+ is PromoProjectGenerator -> PromoStep(projectGenerator)
+ // Legacy for backward compatibility
+ is PythonProjectGenerator -> createLegacyStep(projectGenerator)
+ // Some other generator like node
+ else -> ProjectSettingsStepBase(projectGenerator, callback)
+ }
+
+
+ override fun getActions(
+ generators: List>,
+ callback: AbstractCallback,
+ ): Array {
+ // Show non python actions as a collapsed group
+ val actions = super.getActions(generators, callback)
+ val (pythonActions, nonPythonActions) = actions
+ .partition { it is PyV3ProjectSpecificStep || it is PromoStep && it.generator.isPython }
+ return arrayOf(
+ DefaultActionGroup(PyCharmCommunityCustomizationBundle.message("new.project.python.group.name"), pythonActions),
+ CollapsedActionGroup(PyCharmCommunityCustomizationBundle.message("new.project.other.group.name"), nonPythonActions)
+ )
+ }
+ }
+
+ private companion object {
+ /**
+ * Remove as soon all usages of [PythonProjectGenerator] are dropped
+ */
+ @Suppress("UNCHECKED_CAST", "DEPRECATION")
+ private fun createLegacyStep(projectGenerator: PythonProjectGenerator<*>): ProjectSettingsStepBase {
+ return PythonProjectSpecificSettingsStep(projectGenerator as PythonProjectGenerator, PythonGenerateProjectCallback()) as ProjectSettingsStepBase
+ }
+ }
+}
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/PyV3ProjectSpecificStep.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/PyV3ProjectSpecificStep.kt
new file mode 100644
index 000000000000..54db9d202e0d
--- /dev/null
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/PyV3ProjectSpecificStep.kt
@@ -0,0 +1,12 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.pycharm.community.ide.impl.newProject.impl
+
+import com.intellij.ide.util.projectWizard.AbstractNewProjectStep
+import com.intellij.ide.util.projectWizard.ProjectSettingsStepBase
+import com.jetbrains.python.newProjectWizard.PyV3BaseProjectSettings
+import com.jetbrains.python.newProjectWizard.PyV3ProjectBaseGenerator
+
+internal class PyV3ProjectSpecificStep(
+ generator: PyV3ProjectBaseGenerator<*>,
+ callback: AbstractNewProjectStep.AbstractCallback,
+) : ProjectSettingsStepBase(generator, callback)
\ No newline at end of file
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/PyV3EmptyProjectGenerator.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/PyV3EmptyProjectGenerator.kt
new file mode 100644
index 000000000000..73c486635507
--- /dev/null
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/PyV3EmptyProjectGenerator.kt
@@ -0,0 +1,15 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.pycharm.community.ide.impl.newProject.impl.emptyProject
+
+import com.jetbrains.python.newProjectWizard.PyV3ProjectBaseGenerator
+import com.jetbrains.python.PyBundle
+import com.jetbrains.python.psi.icons.PythonPsiApiIcons
+import org.jetbrains.annotations.Nls
+import javax.swing.Icon
+
+class PyV3EmptyProjectGenerator : PyV3ProjectBaseGenerator(
+ PyV3EmptyProjectSettings(generateWelcomeScript = false), PyV3EmptyProjectUI) {
+ override fun getName(): @Nls String = PyBundle.message("pure.python.project")
+
+ override fun getLogo(): Icon = PythonPsiApiIcons.Python
+}
\ No newline at end of file
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/PyV3EmptyProjectSettings.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/PyV3EmptyProjectSettings.kt
new file mode 100644
index 000000000000..84230a2036cf
--- /dev/null
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/PyV3EmptyProjectSettings.kt
@@ -0,0 +1,29 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.pycharm.community.ide.impl.newProject.impl.emptyProject
+
+import com.intellij.openapi.application.EDT
+import com.intellij.openapi.application.writeAction
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.vfs.VirtualFile
+import com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificSettings
+import com.intellij.pycharm.community.ide.impl.newProject.welcome.PyWelcome
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+class PyV3EmptyProjectSettings(var generateWelcomeScript: Boolean = false) : PyV3ProjectTypeSpecificSettings {
+
+ override suspend fun generateProject(module: Module, baseDir: VirtualFile, sdk: Sdk) {
+ if (!generateWelcomeScript) return
+ val file = writeAction {
+ PyWelcome.prepareFile(module.project, baseDir)
+ }
+ withContext(Dispatchers.EDT) {
+ file.navigate(true)
+ }
+ }
+
+ override fun toString(): String {
+ return super.toString() + ",PyV3EmptyProjectRequest(generateWelcomeScript=$generateWelcomeScript)"
+ }
+}
\ No newline at end of file
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/PyV3EmptyProjectUI.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/PyV3EmptyProjectUI.kt
new file mode 100644
index 000000000000..44c8998a2703
--- /dev/null
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/PyV3EmptyProjectUI.kt
@@ -0,0 +1,14 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.pycharm.community.ide.impl.newProject.impl.emptyProject
+
+import com.intellij.ui.dsl.builder.Panel
+import com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificUI
+import com.intellij.ui.dsl.builder.Row
+import com.intellij.ui.dsl.builder.bindSelected
+import com.jetbrains.python.PyBundle
+
+object PyV3EmptyProjectUI : PyV3ProjectTypeSpecificUI {
+ override fun configureUpperPanel(settings: PyV3EmptyProjectSettings, checkBoxRow: Row, belowCheckBoxes: Panel) {
+ checkBoxRow.checkBox(PyBundle.message("new.project.welcome")).bindSelected(settings::generateWelcomeScript)
+ }
+}
\ No newline at end of file
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/package-info.java b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/package-info.java
new file mode 100644
index 000000000000..f2ba461cedb9
--- /dev/null
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/emptyProject/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+/**
+ * Project without any framework
+ */
+@ApiStatus.Internal
+package com.intellij.pycharm.community.ide.impl.newProject.impl.emptyProject;
+
+import org.jetbrains.annotations.ApiStatus;
\ No newline at end of file
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/package-info.java b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/package-info.java
new file mode 100644
index 000000000000..93b33832ec28
--- /dev/null
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/impl/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+@ApiStatus.Internal
+package com.intellij.pycharm.community.ide.impl.newProject.impl;
+
+import org.jetbrains.annotations.ApiStatus;
\ No newline at end of file
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/steps/PyCharmNewProjectStep.java b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/steps/PyCharmNewProjectStep.java
deleted file mode 100644
index d6466ccc7bb1..000000000000
--- a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/steps/PyCharmNewProjectStep.java
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
-package com.intellij.pycharm.community.ide.impl.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.actionSystem.DefaultActionGroup;
-import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.wm.impl.welcomeScreen.collapsedActionGroup.CollapsedActionGroup;
-import com.intellij.platform.DirectoryProjectGenerator;
-import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle;
-import com.intellij.util.ObjectUtils;
-import com.jetbrains.python.newProject.*;
-import com.jetbrains.python.newProject.steps.PythonProjectSpecificSettingsStep;
-import one.util.streamex.StreamEx;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-
-public final class PyCharmNewProjectStep extends AbstractNewProjectStep {
- public PyCharmNewProjectStep() {
- super(new Customization());
- }
-
- protected static class Customization extends AbstractNewProjectStep.Customization {
- @NotNull
- @Override
- protected AbstractCallback createCallback() {
- return new PythonGenerateProjectCallback<>();
- }
-
- @NotNull
- @Override
- protected DirectoryProjectGenerator createEmptyProjectGenerator() {
- return new PythonBaseProjectGenerator();
- }
-
- @NotNull
- @Override
- protected ProjectSettingsStepBase createProjectSpecificSettingsStep(@NotNull DirectoryProjectGenerator projectGenerator,
- @NotNull AbstractCallback callback) {
- var npwGenerator = ObjectUtils.tryCast(projectGenerator, NewProjectWizardDirectoryGeneratorAdapter.class);
- if (npwGenerator != null) {
- //noinspection unchecked
- return new NewProjectWizardProjectSettingsStep(npwGenerator);
- }
- else {
- return new PythonProjectSpecificSettingsStep<>(projectGenerator, callback);
- }
- }
-
- @Override
- public AnAction[] getActions(@NotNull List extends DirectoryProjectGenerator>> generators,
- @NotNull AbstractCallback callback) {
- generators = new ArrayList<>(generators);
- generators.sort(Comparator.comparing(DirectoryProjectGenerator::getName));
- generators.sort(Comparator.comparingInt(value -> {
- if (value instanceof PyFrameworkProjectGenerator) {
- return -2;
- }
- if (value instanceof PythonProjectGenerator) {
- return -1;
- }
- return 0;
- }));
-
- //noinspection unchecked
- var map = StreamEx.of(generators)
- .map(generator -> new Pair<>(generator, getActions((DirectoryProjectGenerator)generator, callback)))
- .partitioningBy((pair) -> pair.first instanceof PythonProjectGenerator);
-
- var python = new DefaultActionGroup(PyCharmCommunityCustomizationBundle.message("new.project.python.group.name"),
- map.get(true).stream().flatMap(pair -> Arrays.stream(pair.second)).toList());
- var other = new CollapsedActionGroup(PyCharmCommunityCustomizationBundle.message("new.project.other.group.name"),
- map.get(false).stream().flatMap(pair -> Arrays.stream(pair.second)).toList());
- return new AnAction[]{python, other};
- }
- }
-}
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/steps/PythonBaseProjectGenerator.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/steps/PythonBaseProjectGenerator.kt
deleted file mode 100644
index 0b0a8624da0b..000000000000
--- a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/steps/PythonBaseProjectGenerator.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
-package com.intellij.pycharm.community.ide.impl.newProject.steps
-
-import com.intellij.openapi.module.Module
-import com.intellij.openapi.progress.ProcessCanceledException
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.ui.VerticalFlowLayout
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.pycharm.community.ide.impl.newProject.welcome.PyWelcomeGenerator.createWelcomeSettingsPanel
-import com.intellij.pycharm.community.ide.impl.newProject.welcome.PyWelcomeGenerator.welcomeUser
-import com.jetbrains.python.PyBundle
-import com.jetbrains.python.newProject.PyNewProjectSettings
-import com.jetbrains.python.newProject.PythonProjectGenerator
-import com.jetbrains.python.psi.icons.PythonPsiApiIcons
-import com.jetbrains.python.remote.PyProjectSynchronizer
-import com.jetbrains.python.sdk.pythonSdk
-import org.jetbrains.annotations.Nls
-import javax.swing.Icon
-import javax.swing.JPanel
-
-class PythonBaseProjectGenerator : PythonProjectGenerator(true) {
- override fun getName(): @Nls String = PyBundle.message("pure.python.project")
-
- @Throws(ProcessCanceledException::class)
- override fun extendBasePanel(): JPanel = JPanel(VerticalFlowLayout(3, 0)).apply {
- add(createWelcomeSettingsPanel())
- }
-
- override fun getLogo(): Icon = PythonPsiApiIcons.Python
-
- public override fun configureProject(project: Project,
- baseDir: VirtualFile,
- settings: PyNewProjectSettings,
- module: Module,
- synchronizer: PyProjectSynchronizer?) {
- // Super should be called according to its contract unless we sync project explicitly (we do not, so we call super)
- super.configureProject(project, baseDir, settings, module, synchronizer)
- module.pythonSdk = settings.sdk
- welcomeUser(project, baseDir, module)
- }
-
- override fun getNewProjectPrefix(): String = "pythonProject"
-
- override fun supportsWelcomeScript(): Boolean = true
-}
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/steps/PythonGenerateProjectCallback.java b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/steps/PythonGenerateProjectCallback.java
index 2430292fad53..d884b59077bb 100644
--- a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/steps/PythonGenerateProjectCallback.java
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/steps/PythonGenerateProjectCallback.java
@@ -1,35 +1,25 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.pycharm.community.ide.impl.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;
import com.intellij.platform.ProjectGeneratorPeer;
-import com.intellij.pycharm.community.ide.impl.newProject.welcome.PyWelcomeSettings;
-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.steps.ProjectSpecificSettingsStep;
-import com.jetbrains.python.newProject.steps.PythonProjectSpecificSettingsStep;
-import com.intellij.pycharm.community.ide.impl.newProject.welcome.PyWelcomeSettings;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.Arrays;
-import java.util.Optional;
-
-
+/**
+ * @deprecated See {@link com.jetbrains.python.newProjectWizard}
+ */
+@Deprecated
public final class PythonGenerateProjectCallback extends AbstractNewProjectStep.AbstractCallback {
@Override
@@ -37,11 +27,6 @@ public final class PythonGenerateProjectCallback
if (!(step instanceof ProjectSpecificSettingsStep settingsStep)) return;
// FIXME: pass welcome script creation via settings
- if (settingsStep 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();
@@ -61,20 +46,6 @@ public final class PythonGenerateProjectCallback
SdkConfigurationUtil.setDirectoryProjectSdk(newProject, sdk);
((PythonProjectGenerator>)generator).afterProjectGenerated(newProject);
}
-
- if (settingsStep instanceof PythonProjectSpecificSettingsStep newStep) {
- // init git repostory
- if (PropertiesComponent.getInstance().getBoolean("PyCharm.NewProject.Git", false)) {
- ModuleManager moduleManager = ModuleManager.getInstance(newProject);
- Optional 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
@@ -87,8 +58,8 @@ public final class PythonGenerateProjectCallback
@Nullable
private static Object computeProjectSettings(DirectoryProjectGenerator> generator,
- final ProjectSpecificSettingsStep settingsStep,
- @NotNull final ProjectGeneratorPeer projectGeneratorPeer) {
+ final ProjectSpecificSettingsStep settingsStep,
+ @NotNull final ProjectGeneratorPeer projectGeneratorPeer) {
Object projectSettings = null;
if (generator instanceof PythonProjectGenerator> projectGenerator) {
projectSettings = projectGenerator.getProjectSettings();
@@ -99,8 +70,6 @@ public final class PythonGenerateProjectCallback
if (projectSettings instanceof PyNewProjectSettings newProjectSettings) {
newProjectSettings.setSdk(settingsStep.getSdk());
newProjectSettings.setInterpreterInfoForStatistics(settingsStep.getInterpreterInfoForStatistics());
- newProjectSettings.setInstallFramework(settingsStep.installFramework());
- newProjectSettings.setCreateWelcomeScript(settingsStep.createWelcomeScript());
newProjectSettings.setRemotePath(settingsStep.getRemotePath());
}
return projectSettings;
diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/welcome/PyWelcome.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/welcome/PyWelcome.kt
index a1cd2a0c7c31..e4a96843c6d2 100644
--- a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/welcome/PyWelcome.kt
+++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/newProject/welcome/PyWelcome.kt
@@ -43,6 +43,7 @@ import com.intellij.pycharm.community.ide.impl.newProject.welcome.PyWelcomeColle
import com.intellij.pycharm.community.ide.impl.newProject.welcome.PyWelcomeCollector.logWelcomeRunConfiguration
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.builder.selected
+import com.intellij.util.concurrency.annotations.RequiresWriteLock
import com.intellij.xdebugger.XDebuggerUtil
import com.jetbrains.python.PythonPluginDisposable
import com.jetbrains.python.psi.LanguageLevel
@@ -94,7 +95,7 @@ internal object PyWelcomeGenerator {
}
}
-private object PyWelcome {
+object PyWelcome {
private val LOG = Logger.getInstance(PyWelcome::class.java)
@CalledInAny
@@ -175,12 +176,14 @@ private object PyWelcome {
}
@CalledInAny
- private fun expandProjectTree(project: Project,
- toolWindowManager: ToolWindowManager,
- baseDir: VirtualFile,
- module: Module?,
- file: VirtualFile?,
- point: ProjectViewPoint) {
+ private fun expandProjectTree(
+ project: Project,
+ toolWindowManager: ToolWindowManager,
+ baseDir: VirtualFile,
+ module: Module?,
+ file: VirtualFile?,
+ point: ProjectViewPoint,
+ ) {
// the approach was taken from com.intellij.platform.PlatformProjectViewOpener
val toolWindow = toolWindowManager.getToolWindow(ToolWindowId.PROJECT_VIEW)
@@ -209,17 +212,13 @@ private object PyWelcome {
}
}
- private fun prepareFile(project: Project, baseDir: VirtualFile): PsiFile? {
+ @RequiresWriteLock
+ fun prepareFile(project: Project, baseDir: VirtualFile): PsiFile {
val file = kotlin.runCatching { baseDir.createChildData(this, "main.py") }
.onFailure { PyWelcomeCollector.logWelcomeScript(project, ScriptResult.NO_VFILE) }
.getOrThrow()
- val psiFile = PsiManager.getInstance(project).findFile(file)
- if (psiFile == null) {
- LOG.warn("Unable to get psi for $file")
- PyWelcomeCollector.logWelcomeScript(project, ScriptResult.NO_PSI)
- return null
- }
+ val psiFile = PsiManager.getInstance(project).findFile(file) ?: error("File $file was just created, but not found in PSI")
writeText(project, psiFile)?.also { line ->
PyWelcomeCollector.logWelcomeScript(project, ScriptResult.CREATED)
@@ -278,10 +277,12 @@ private object PyWelcome {
return breakpointLine
}
- private class ProjectViewListener(private val project: Project,
- private val baseDir: VirtualFile,
- private val module: Module?,
- private val file: VirtualFile?) : ToolWindowManagerListener, Disposable {
+ private class ProjectViewListener(
+ private val project: Project,
+ private val baseDir: VirtualFile,
+ private val module: Module?,
+ private val file: VirtualFile?,
+ ) : ToolWindowManagerListener, Disposable {
private var toolWindowRegistered = false
diff --git a/python/intellij.python.community.impl.iml b/python/intellij.python.community.impl.iml
index ec31a369c6fe..bbe64ca1edde 100644
--- a/python/intellij.python.community.impl.iml
+++ b/python/intellij.python.community.impl.iml
@@ -151,5 +151,6 @@
+
\ No newline at end of file
diff --git a/python/openapi/src/com/jetbrains/python/newProject/PyNewProjectSettings.java b/python/openapi/src/com/jetbrains/python/newProject/PyNewProjectSettings.java
index 1d9054bb0850..a69aee3d1374 100644
--- a/python/openapi/src/com/jetbrains/python/newProject/PyNewProjectSettings.java
+++ b/python/openapi/src/com/jetbrains/python/newProject/PyNewProjectSettings.java
@@ -22,14 +22,11 @@ import org.jetbrains.annotations.Nullable;
* Project generation settings selected on the first page of the new project dialog.
*
* @author catherine
+ * @deprecated Use {@link com.jetbrains.python.newProjectWizard}
*/
+@Deprecated
public class PyNewProjectSettings {
private Sdk mySdk;
- private boolean myInstallFramework;
- /**
- * Is something like main.py should be created
- */
- private boolean myCreateWelcomeScript;
/**
* Path on remote server for remote project
*/
@@ -48,14 +45,6 @@ public class PyNewProjectSettings {
mySdk = sdk;
}
- public void setInstallFramework(final boolean installFramework) {
- myInstallFramework = installFramework;
- }
-
- public boolean installFramework() {
- return myInstallFramework;
- }
-
public final void setRemotePath(@Nullable final String remotePath) {
myRemotePath = remotePath;
}
@@ -73,12 +62,4 @@ public class PyNewProjectSettings {
public final String getRemotePath() {
return myRemotePath;
}
-
- public final boolean createWelcomeScript() {
- return myCreateWelcomeScript;
- }
-
- public final void setCreateWelcomeScript(boolean createWelcomeScript) {
- myCreateWelcomeScript = createWelcomeScript;
- }
}
diff --git a/python/pluginResources/messages/PyBundle.properties b/python/pluginResources/messages/PyBundle.properties
index e9a15e5a0d3b..50eb6b5a6fff 100644
--- a/python/pluginResources/messages/PyBundle.properties
+++ b/python/pluginResources/messages/PyBundle.properties
@@ -359,6 +359,7 @@ 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.new.error.no.absolute=Path must be absolute
# Poetry package manager and SDK
python.sdk.poetry.executable.not.found=Poetry executable is not found
@@ -495,6 +496,10 @@ sdk.create.type.base.conda=Base conda
sdk.create.type.custom=Custom environment
sdk.create.python.version=Python version:
sdk.create.simple.venv.hint=Python virtual environment will be created in the project root: {0}.venv
+sdk.create.conda.executable.path=Path to conda:
+sdk.create.conda.missing.text=No conda executable found.
+sdk.create.conda.install.fix=Install Miniconda
+sdk.create.simple.venv.hint=Python virtual environment will be created in the project root: {0}
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:
@@ -1535,4 +1540,5 @@ python.toolwindow.packages.deleting.text=Uninstalling\u2026
progress.text.installing=Installing\u2026
package.install.with.options.dialog.message=Options:
package.install.with.options.dialog.title=Package Install With Options
-python.toolwindow.packages.collapse.all.action=Collapse All
\ No newline at end of file
+python.toolwindow.packages.collapse.all.action=Collapse All
+django.template.language=Template Language
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProject/PythonProjectGenerator.java b/python/src/com/jetbrains/python/newProject/PythonProjectGenerator.java
index 9b4b416cd7ad..84cb54442087 100644
--- a/python/src/com/jetbrains/python/newProject/PythonProjectGenerator.java
+++ b/python/src/com/jetbrains/python/newProject/PythonProjectGenerator.java
@@ -48,33 +48,9 @@ import java.util.function.Consumer;
/**
- * This class encapsulates remote settings, so one should extend it for any python project that supports remote generation, at least
- * Instead of {@link #generateProject(Project, VirtualFile, PyNewProjectSettings, Module)} inheritor shall use
- * {@link #configureProject(Project, VirtualFile, PyNewProjectSettings, Module, PyProjectSynchronizer)}*
- * or {@link #configureProjectNoSettings(Project, VirtualFile, Module)} (see difference below)
- *
- * If your project does not support remote projects generation, be sure to set flag in ctor:{@link #PythonProjectGenerator(boolean)}
- *
- * Module vs PyCharm projects
- *
- * When you create project in PyCharm it always calls {@link #configureProject(Project, VirtualFile, PyNewProjectSettings, Module, PyProjectSynchronizer)},
- * but in Intellij Plugin settings are not ready to the moment of project creation, so there are 2 ways to support plugin:
- *
- * - Do not lean on settings at all. You simply implement {@link #configureProjectNoSettings(Project, VirtualFile, Module)}
- * This way is common for project templates.
- *
- * - Implement framework as facet. {@link #configureProject(Project, VirtualFile, PyNewProjectSettings, Module, PyProjectSynchronizer)}
- * will never be called in this case, so you can use "onFacetCreated" event of facet provider
- *
- *
- *
- * How to report framework installation failures
- * {@link PyNewProjectSettings#getSdk()} may return null, or something else may prevent package installation.
- * Use {@link #reportPackageInstallationFailure(String, Pair)} in this case.
- *
- *
- * @param project settings
+ * @deprecated Use {@link com.jetbrains.python.newProjectWizard}
*/
+@Deprecated
public abstract class PythonProjectGenerator extends DirectoryProjectGeneratorBase {
public static final PyNewProjectSettings NO_SETTINGS = new PyNewProjectSettings();
private static final Logger LOGGER = Logger.getInstance(PythonProjectGenerator.class);
diff --git a/python/src/com/jetbrains/python/newProject/PythonPromoProjectGenerator.kt b/python/src/com/jetbrains/python/newProject/PythonPromoProjectGenerator.kt
deleted file mode 100644
index bce664c6792f..000000000000
--- a/python/src/com/jetbrains/python/newProject/PythonPromoProjectGenerator.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-// 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
-
-import javax.swing.JPanel
-
-interface PythonPromoProjectGenerator {
- fun createPromoPanel(): JPanel
-}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProject/promotion/PromoProjectGenerator.kt b/python/src/com/jetbrains/python/newProject/promotion/PromoProjectGenerator.kt
new file mode 100644
index 000000000000..efd2b27a65d6
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProject/promotion/PromoProjectGenerator.kt
@@ -0,0 +1,21 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.newProject.promotion
+
+import com.intellij.facet.ui.ValidationResult
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.platform.DirectoryProjectGenerator
+import com.jetbrains.python.newProjectWizard.PyV3BaseProjectSettings
+import javax.swing.JPanel
+
+/**
+ * [isPython] shows this promotion in "python" section ("other" otherwise)
+ */
+abstract class PromoProjectGenerator(val isPython: Boolean) : DirectoryProjectGenerator {
+ abstract fun createPromoPanel(): JPanel
+
+ override fun generateProject(project: Project, baseDir: VirtualFile, settings: PyV3BaseProjectSettings, module: Module) = Unit
+
+ override fun validate(baseDirPath: String): ValidationResult = ValidationResult.OK
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProject/promotion/PromoStep.kt b/python/src/com/jetbrains/python/newProject/promotion/PromoStep.kt
new file mode 100644
index 000000000000..1a8d7c6ce338
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProject/promotion/PromoStep.kt
@@ -0,0 +1,14 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.newProject.promotion
+
+import com.intellij.ide.util.projectWizard.AbstractNewProjectStep
+import com.intellij.ide.util.projectWizard.ProjectSettingsStepBase
+import com.jetbrains.python.newProjectWizard.PyV3BaseProjectSettings
+import javax.swing.JPanel
+
+class PromoStep(val generator: PromoProjectGenerator) : ProjectSettingsStepBase(generator, AbstractNewProjectStep.AbstractCallback()) {
+ override fun createBasePanel(): JPanel {
+ myCreateButton.isEnabled = false
+ return generator.createPromoPanel()
+ }
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProject/promotion/package-info.java b/python/src/com/jetbrains/python/newProject/promotion/package-info.java
new file mode 100644
index 000000000000..8e26fa5c6e25
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProject/promotion/package-info.java
@@ -0,0 +1,8 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+/**
+ * For promotions: things that aren't available in community.
+ */
+@ApiStatus.Internal
+package com.jetbrains.python.newProject.promotion;
+
+import org.jetbrains.annotations.ApiStatus;
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java b/python/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java
index 8126fb57367a..9418acf5a55f 100644
--- a/python/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java
+++ b/python/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java
@@ -50,6 +50,10 @@ import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
+/**
+ * @deprecated Use {@link com.jetbrains.python.newProjectWizard}
+ */
+@Deprecated
public class ProjectSpecificSettingsStep extends ProjectSettingsStepBase implements DumbAware {
private boolean myInstallFramework;
private @Nullable PyAddSdkGroupPanel myInterpreterPanel;
@@ -181,7 +185,7 @@ public class ProjectSpecificSettingsStep extends
if (validationErrors.isEmpty()) {
// Once can't create anything on immutable SDK
- var sdk = (interpreterPanel != null) ? interpreterPanel.getSdk() : null;
+ var sdk = (interpreterPanel != null) ? interpreterPanel.getSdk() : null;
if (sdk != null && isImmutableSdk(sdk)) {
validationErrors = List.of(
PyBundle.message("python.unknown.project.synchronizer.this.interpreter.type.does.not.support.remote.project.creation"));
@@ -308,7 +312,8 @@ public class ProjectSpecificSettingsStep extends
final Sdk preferredSdk = existingSdks.stream().findFirst().orElse(null);
final String newProjectPath = getProjectLocation();
- final PyAddNewEnvironmentPanel newEnvironmentPanel = new PyAddNewEnvironmentPanel(allExistingSdks, newProjectPath, preferredEnvironment);
+ final PyAddNewEnvironmentPanel newEnvironmentPanel =
+ new PyAddNewEnvironmentPanel(allExistingSdks, newProjectPath, preferredEnvironment);
final PyAddExistingSdkPanel existingSdkPanel = new PyAddExistingSdkPanel(null, null, existingSdks, newProjectPath, preferredSdk);
PyAddSdkPanel defaultPanel = PySdkSettings.getInstance().getUseNewEnvironmentForNewProject() ?
diff --git a/python/src/com/jetbrains/python/newProject/steps/PythonProjectSpecificSettingsStep.kt b/python/src/com/jetbrains/python/newProject/steps/PythonProjectSpecificSettingsStep.kt
index c61b2a842d85..095592fe1a40 100644
--- a/python/src/com/jetbrains/python/newProject/steps/PythonProjectSpecificSettingsStep.kt
+++ b/python/src/com/jetbrains/python/newProject/steps/PythonProjectSpecificSettingsStep.kt
@@ -28,12 +28,13 @@ import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.bindText
import com.intellij.ui.dsl.builder.panel
import com.intellij.util.PlatformUtils
+import com.intellij.util.SystemProperties
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.PyNewProjectSettings
import com.jetbrains.python.newProject.PythonProjectGenerator
-import com.jetbrains.python.newProject.PythonPromoProjectGenerator
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
+import com.jetbrains.python.newProject.promotion.PromoProjectGenerator
import com.jetbrains.python.psi.PyUtil
import com.jetbrains.python.sdk.PyLazySdk
import com.jetbrains.python.sdk.add.v2.PythonAddNewEnvironmentPanel
@@ -44,16 +45,20 @@ import java.nio.file.Path
import java.util.*
import javax.swing.JPanel
+
+/**
+ * @deprecated Use [com.jetbrains.python.newProjectWizard]
+ */
+@Deprecated("use com.jetbrains.python.newProjectWizard")
class PythonProjectSpecificSettingsStep(
projectGenerator: DirectoryProjectGenerator,
callback: AbstractNewProjectStep.AbstractCallback,
-)
- : ProjectSpecificSettingsStep(projectGenerator, callback), DumbAware {
+) : ProjectSpecificSettingsStep(projectGenerator, callback), DumbAware {
private val propertyGraph = PropertyGraph()
private val projectName = propertyGraph.property("")
private val projectLocation = propertyGraph.property("")
- private val projectLocationFlow = MutableStateFlow(projectLocation.get())
+ private val projectLocationFlow = MutableStateFlow(Path.of(SystemProperties.getUserHome()))
private val locationHint = propertyGraph.property("").apply {
dependsOn(projectName, ::updateHint)
dependsOn(projectLocation, ::updateHint)
@@ -65,7 +70,7 @@ class PythonProjectSpecificSettingsStep(
init {
projectLocation.afterChange {
- projectLocationFlow.value = projectLocation.get()
+ projectLocationFlow.value = Path.of(projectLocation.get())
}
}
@@ -103,7 +108,7 @@ class PythonProjectSpecificSettingsStep(
override fun createBasePanel(): JPanel {
val projectGenerator = myProjectGenerator
- if (projectGenerator is PythonPromoProjectGenerator) {
+ if (projectGenerator is PromoProjectGenerator) {
myCreateButton.isEnabled = false
myLocationField = TextFieldWithBrowseButton()
return projectGenerator.createPromoPanel()
@@ -208,6 +213,7 @@ class PythonProjectSpecificSettingsStep(
}
override fun getSdk(): Sdk {
+ // It is here only for DS, not used in PyCharm
return PyLazySdk("Uninitialized environment") { interpreterPanel?.getSdk() }
}
diff --git a/python/src/com/jetbrains/python/newProjectWizard/PathValidator.kt b/python/src/com/jetbrains/python/newProjectWizard/PathValidator.kt
new file mode 100644
index 000000000000..b5ad2f57ab7e
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProjectWizard/PathValidator.kt
@@ -0,0 +1,35 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.newProjectWizard
+
+import com.intellij.openapi.ui.validation.CHECK_NON_EMPTY
+import com.intellij.openapi.ui.validation.CHECK_NO_RESERVED_WORDS
+import com.intellij.openapi.ui.validation.CHECK_NO_WHITESPACES
+import com.intellij.openapi.util.NlsSafe
+import com.jetbrains.python.PyBundle
+import com.jetbrains.python.Result
+import java.nio.file.InvalidPathException
+import java.nio.file.Path
+import java.nio.file.Paths
+
+/**
+ * Returns either error or [Path] for project path
+ */
+fun validateProjectPathAndGetPath(baseDirPath: String): Result {
+ val path = try {
+ Paths.get(baseDirPath)
+ }
+ catch (e: InvalidPathException) {
+ return Result.Failure(e.reason)
+ }
+
+ if (!path.isAbsolute) {
+ return Result.Failure(PyBundle.message("python.sdk.new.error.no.absolute"))
+ }
+
+ for (validator in arrayOf(CHECK_NON_EMPTY, CHECK_NO_WHITESPACES, CHECK_NO_RESERVED_WORDS)) {
+ validator.curry { baseDirPath }.validate()?.let {
+ return Result.Failure(it.message)
+ }
+ }
+ return Result.Success(path)
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProjectWizard/PyV3BaseProjectSettings.kt b/python/src/com/jetbrains/python/newProjectWizard/PyV3BaseProjectSettings.kt
new file mode 100644
index 000000000000..fc0138703a76
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProjectWizard/PyV3BaseProjectSettings.kt
@@ -0,0 +1,52 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.newProjectWizard
+
+import com.intellij.openapi.GitRepositoryInitializer
+import com.intellij.openapi.application.EDT
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.platform.ide.progress.withBackgroundProgress
+import com.jetbrains.python.PyBundle
+import com.jetbrains.python.newProject.collector.PythonNewProjectWizardCollector.logPythonNewProjectGenerated
+import com.jetbrains.python.sdk.ModuleOrProject
+import com.jetbrains.python.sdk.add.PySdkCreator
+import com.jetbrains.python.sdk.pythonSdk
+import com.jetbrains.python.sdk.setAssociationToModule
+import com.jetbrains.python.statistics.version
+import kotlinx.coroutines.*
+
+/**
+ * Settings each Python project has: [sdkCreator] and [createGitRepository]
+ */
+class PyV3BaseProjectSettings(var createGitRepository: Boolean = false) {
+ lateinit var sdkCreator: PySdkCreator
+
+ suspend fun generateAndGetSdk(module: Module, baseDir: VirtualFile): Sdk = coroutineScope {
+ val project = module.project
+ if (createGitRepository) {
+ launch(CoroutineName("Generating git") + Dispatchers.IO) {
+ withBackgroundProgress(project, PyBundle.message("new.project.git")) {
+ GitRepositoryInitializer.getInstance()?.initRepository(project, baseDir, true) ?: error("No git service available")
+ }
+ }
+ }
+ val (sdk, statistics) = withContext(Dispatchers.EDT) {
+ Pair(sdkCreator.getSdk(ModuleOrProject.ModuleAndProject(module)), sdkCreator.createStatisticsInfo())
+ }
+ sdk.setAssociationToModule(module)
+ module.pythonSdk = sdk
+ if (statistics != null) {
+ logPythonNewProjectGenerated(statistics,
+ sdk.version,
+ this::class.java,
+ emptyList())
+ }
+ return@coroutineScope sdk
+ }
+
+
+ override fun toString(): String {
+ return "PyV3ProjectGenerationRequest(createGitRepository=$createGitRepository)"
+ }
+}
diff --git a/python/src/com/jetbrains/python/newProjectWizard/PyV3ProjectBaseGenerator.kt b/python/src/com/jetbrains/python/newProjectWizard/PyV3ProjectBaseGenerator.kt
new file mode 100644
index 000000000000..dfa0f05e3e52
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProjectWizard/PyV3ProjectBaseGenerator.kt
@@ -0,0 +1,58 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.newProjectWizard
+
+import com.intellij.facet.ui.ValidationResult
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.components.service
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.platform.DirectoryProjectGenerator
+import com.intellij.platform.ProjectGeneratorPeer
+import com.intellij.util.SystemProperties
+import com.jetbrains.python.Result
+import com.jetbrains.python.newProjectWizard.impl.PyV3GeneratorPeer
+import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMode
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+import org.jetbrains.annotations.Nls
+import java.nio.file.Path
+
+/**
+ * Extend this class to register a new project generator.
+ * [typeSpecificSettings] are settings defaults.
+ * [typeSpecificUI] is a UI to display these settings and bind then using Kotlin DSL UI
+ * [allowedInterpreterTypes] limits a list of allowed interpreters (all interpreters are allowed by default)
+ */
+abstract class PyV3ProjectBaseGenerator(
+ private val typeSpecificSettings: TYPE_SPECIFIC_SETTINGS,
+ private val typeSpecificUI: PyV3ProjectTypeSpecificUI?,
+ private val allowedInterpreterTypes: Set? = null,
+) : DirectoryProjectGenerator {
+ private val baseSettings = PyV3BaseProjectSettings()
+ private val projectPathFlow = MutableStateFlow(Path.of(SystemProperties.getUserHome()))
+
+ override fun generateProject(project: Project, baseDir: VirtualFile, settings: PyV3BaseProjectSettings, module: Module) {
+ val coroutineScope = project.service().coroutineScope
+ coroutineScope.launch {
+ typeSpecificSettings.generateProject(module, baseDir, settings.generateAndGetSdk(module, baseDir))
+ }
+ }
+
+ override fun createPeer(): ProjectGeneratorPeer =
+ PyV3GeneratorPeer(baseSettings, projectPathFlow, typeSpecificUI?.let { Pair(it, typeSpecificSettings) }, allowedInterpreterTypes)
+
+ override fun validate(baseDirPath: String): ValidationResult =
+ when (val pathOrError = validateProjectPathAndGetPath(baseDirPath)) {
+ is Result.Success -> {
+ projectPathFlow.value = pathOrError.result
+ ValidationResult.OK
+ }
+ is Result.Failure<*, @Nls String> -> ValidationResult(pathOrError.error)
+ }
+
+
+ @Service(Service.Level.PROJECT)
+ private class MyService(val coroutineScope: CoroutineScope)
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProjectWizard/PyV3ProjectTypeSpecificSettings.kt b/python/src/com/jetbrains/python/newProjectWizard/PyV3ProjectTypeSpecificSettings.kt
new file mode 100644
index 000000000000..d4b8279f408f
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProjectWizard/PyV3ProjectTypeSpecificSettings.kt
@@ -0,0 +1,24 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.newProjectWizard
+
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.vfs.VirtualFile
+
+/**
+ * Implementation must have fields and generate project-specific (like Django) things using them i.e:
+ * ```kotlin
+ * class SpamGeneratorSettings(var queueSize:Int): PyV3ProjectTypeSpecificSettings {
+ * override fun generateProject(module: Module, baseDir: VirtualFile, sdk: Sdk) {
+ * generateSpam(queueSize, module, baseDir, sdk)
+ * }
+ * }
+ * ```
+ */
+fun interface PyV3ProjectTypeSpecificSettings {
+ /**
+ * Generate project-specific things in [baseDir].
+ * You might need to [installPackages] on [sdk]
+ */
+ suspend fun generateProject(module: Module, baseDir: VirtualFile, sdk: Sdk)
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProjectWizard/PyV3ProjectTypeSpecificUI.kt b/python/src/com/jetbrains/python/newProjectWizard/PyV3ProjectTypeSpecificUI.kt
new file mode 100644
index 000000000000..f8e2049cb986
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProjectWizard/PyV3ProjectTypeSpecificUI.kt
@@ -0,0 +1,26 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.newProjectWizard
+
+import com.intellij.openapi.util.NlsSafe
+import com.intellij.ui.dsl.builder.Panel
+import com.intellij.ui.dsl.builder.Row
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Binds [PROJECT_SPECIFIC_SETTINGS] to Kotlin DSL UI.
+ */
+interface PyV3ProjectTypeSpecificUI {
+
+ /**
+ * Upper panel right below the project interpreter path.
+ * [checkBoxRow] is a row with "generate git" checkbox.
+ * [belowCheckBoxes] is a panel below it
+ */
+ fun configureUpperPanel(settings: PROJECT_SPECIFIC_SETTINGS, checkBoxRow: Row, belowCheckBoxes: Panel) = Unit
+
+ /**
+ * If you need to show something in "advanced settings". You also have a flow with a project name calculated from a project path,
+ * you might bind it to the cell
+ */
+ val advancedSettings: (Panel.(settings: PROJECT_SPECIFIC_SETTINGS, projectName: Flow<@NlsSafe String>) -> Unit)? get() = null
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProjectWizard/PyV3Utils.kt b/python/src/com/jetbrains/python/newProjectWizard/PyV3Utils.kt
new file mode 100644
index 000000000000..5c2117b533a9
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProjectWizard/PyV3Utils.kt
@@ -0,0 +1,23 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.newProjectWizard
+
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.projectRoots.Sdk
+import com.jetbrains.python.packaging.common.PythonSimplePackageSpecification
+import com.jetbrains.python.packaging.management.PythonPackageManager
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.supervisorScope
+
+/**
+ * Install [packages] to [sdk]
+ */
+suspend fun installPackages(project: Project, sdk: Sdk, vararg packages: String) {
+ val packageManager = PythonPackageManager.forSdk(project, sdk)
+ supervisorScope { // Not install other packages if one failed
+ for (packageName in packages) {
+ launch {
+ packageManager.installPackage(PythonSimplePackageSpecification(packageName, null, null), emptyList()).getOrThrow()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProjectWizard/impl/Py3VUI.kt b/python/src/com/jetbrains/python/newProjectWizard/impl/Py3VUI.kt
new file mode 100644
index 000000000000..fb39fe5a43ac
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProjectWizard/impl/Py3VUI.kt
@@ -0,0 +1,50 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.newProjectWizard.impl
+
+import com.intellij.ui.dsl.builder.bindSelected
+import com.intellij.ui.dsl.builder.panel
+import com.intellij.util.concurrency.annotations.RequiresEdt
+import com.jetbrains.python.PyBundle
+import com.jetbrains.python.newProjectWizard.PyV3BaseProjectSettings
+import com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificSettings
+import com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificUI
+import com.jetbrains.python.sdk.add.PySdkCreator
+import com.jetbrains.python.sdk.add.v2.PythonAddNewEnvironmentPanel
+import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMode
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import java.nio.file.Path
+import javax.swing.JComponent
+import kotlin.io.path.name
+
+
+internal class Py3VUI @RequiresEdt constructor(
+ baseSettings: PyV3BaseProjectSettings,
+ projectPath: StateFlow,
+ specificUiAndSettings: Pair, TYPE_SPECIFIC_SETTINGS>?,
+ allowedInterpreterTypes: Set? = null,
+) {
+ private val sdkPanel = PythonAddNewEnvironmentPanel(projectPath, allowedInterpreterTypes)
+
+ private val _mainPanel = panel {
+ val checkBoxRow = row {
+ checkBox(PyBundle.message("new.project.git")).bindSelected(baseSettings::createGitRepository)
+ }
+ specificUiAndSettings?.first?.configureUpperPanel(specificUiAndSettings.second, checkBoxRow, this)
+ sdkPanel.buildPanel(this)
+ specificUiAndSettings?.first?.advancedSettings?.let {
+ collapsibleGroup(PyBundle.message("black.advanced.settings.panel.title")) {
+ it(this, specificUiAndSettings.second, projectPath.map { it.name })
+ }
+ }
+ }.apply {
+ sdkPanel.onShown()
+ }
+
+ val mainPanel: JComponent = _mainPanel
+
+ fun applyAndGetSdkCreator(): PySdkCreator {
+ _mainPanel.apply()
+ return sdkPanel
+ }
+}
diff --git a/python/src/com/jetbrains/python/newProjectWizard/impl/PyV3GeneratorPeer.kt b/python/src/com/jetbrains/python/newProjectWizard/impl/PyV3GeneratorPeer.kt
new file mode 100644
index 000000000000..67d05b2785c3
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProjectWizard/impl/PyV3GeneratorPeer.kt
@@ -0,0 +1,37 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.newProjectWizard.impl
+
+import com.intellij.ide.util.projectWizard.SettingsStep
+import com.intellij.openapi.ui.ValidationInfo
+import com.intellij.platform.ProjectGeneratorPeer
+import com.jetbrains.python.newProjectWizard.PyV3BaseProjectSettings
+import com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificSettings
+import com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificUI
+import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMode
+import kotlinx.coroutines.flow.StateFlow
+import java.nio.file.Path
+import javax.swing.JComponent
+
+class PyV3GeneratorPeer(
+ baseSettings: PyV3BaseProjectSettings,
+ projectPath: StateFlow,
+ specificUiAndSettings: Pair, TYPE_SPECIFIC_SETTINGS>?,
+ allowedInterpreterTypes:Set?
+) : ProjectGeneratorPeer {
+ private val settings = baseSettings
+ private val panel: Py3VUI<*> = Py3VUI(settings, projectPath, specificUiAndSettings, allowedInterpreterTypes)
+
+
+ override fun getComponent(): JComponent = panel.mainPanel
+
+ override fun buildUI(settingsStep: SettingsStep) = Unit
+
+ override fun getSettings(): PyV3BaseProjectSettings {
+ settings.sdkCreator = panel.applyAndGetSdkCreator()
+ return settings
+ }
+
+ override fun validate(): ValidationInfo? = null // We validate UI with Kotlin DSL UI form
+
+ override fun isBackgroundJobRunning(): Boolean = false
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProjectWizard/impl/package-info.java b/python/src/com/jetbrains/python/newProjectWizard/impl/package-info.java
new file mode 100644
index 000000000000..6624af217c13
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProjectWizard/impl/package-info.java
@@ -0,0 +1,6 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+
+@ApiStatus.Internal
+package com.jetbrains.python.newProjectWizard.impl;
+
+import org.jetbrains.annotations.ApiStatus;
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/newProjectWizard/package-info.java b/python/src/com/jetbrains/python/newProjectWizard/package-info.java
new file mode 100644
index 000000000000..9adddfd1d193
--- /dev/null
+++ b/python/src/com/jetbrains/python/newProjectWizard/package-info.java
@@ -0,0 +1,15 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+/**
+ * Say, you need to create a new project for framework "Foo".
+ * There are three items to implement:
+ *
+ * {@link com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificSettings} is a something that stores settings and generates project
+ * based on them (and other things like SDK and module).
+ *
+ * {@link com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificUI} is a class that binds settings, mentioned above, to the Kotlin DSL UI panel.
+ *
+ * {@link com.jetbrains.python.newProjectWizard.PyV3ProjectBaseGenerator} connects these two and must be registered as EP.
+ *
+ *
+ */
+package com.jetbrains.python.newProjectWizard;
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/PySdkCreator.kt b/python/src/com/jetbrains/python/sdk/add/PySdkCreator.kt
new file mode 100644
index 000000000000..1f90de9043df
--- /dev/null
+++ b/python/src/com/jetbrains/python/sdk/add/PySdkCreator.kt
@@ -0,0 +1,15 @@
+// Copyright 2000-2024 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
+
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.util.concurrency.annotations.RequiresEdt
+import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
+import com.jetbrains.python.sdk.ModuleOrProject
+
+interface PySdkCreator {
+ @RequiresEdt
+ fun getSdk(moduleOrProject: ModuleOrProject): Sdk
+
+ @RequiresEdt
+ fun createStatisticsInfo(): InterpreterStatisticsInfo? = null
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/CondaExistingEnvironmentSelector.kt b/python/src/com/jetbrains/python/sdk/add/v2/CondaExistingEnvironmentSelector.kt
index 699330f378bc..39a8d5cf3243 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/CondaExistingEnvironmentSelector.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/CondaExistingEnvironmentSelector.kt
@@ -13,6 +13,7 @@ import com.intellij.ui.dsl.builder.bindItem
import com.intellij.ui.layout.predicate
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
+import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
import com.jetbrains.python.statistics.InterpreterCreationMode
@@ -94,7 +95,7 @@ class CondaExistingEnvironmentSelector(model: PythonAddInterpreterModel) : Pytho
//}
}
- override fun getOrCreateSdk(): Sdk {
+ override fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Sdk {
return model.selectCondaEnvironment(state.selectedCondaEnv.get()!!.envIdentity)
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/CondaNewEnvironmentCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/CondaNewEnvironmentCreator.kt
index 7004d6f0fc2f..d36dae9f41b1 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/CondaNewEnvironmentCreator.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/CondaNewEnvironmentCreator.kt
@@ -12,13 +12,18 @@ import com.intellij.ui.dsl.listCellRenderer.textListCellRenderer
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.psi.LanguageLevel
+import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.add.target.conda.condaSupportedLanguages
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest
import com.jetbrains.python.statistics.InterpreterCreationMode
import com.jetbrains.python.statistics.InterpreterType
-import java.io.File
+import com.jetbrains.python.ui.flow.bindText
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import java.nio.file.Path
+import kotlin.io.path.name
-class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel) : PythonNewEnvironmentCreator(model) {
+class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel, private val projectPath: StateFlow?) : PythonNewEnvironmentCreator(model) {
private lateinit var pythonVersion: ObservableMutableProperty
private lateinit var versionComboBox: ComboBox
@@ -32,8 +37,11 @@ class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel)
.component
}
row(message("sdk.create.custom.conda.env.name")) {
- textField()
+ val envName = textField()
.bindText(model.state.newCondaEnvName)
+ if (projectPath != null) {
+ envName.bindText(projectPath.map { it.name })
+ }
}
executableSelector(model.state.condaExecutable,
@@ -46,10 +54,10 @@ class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel)
}
override fun onShown() {
- model.state.newCondaEnvName.set(model.projectPath.value.substringAfterLast(File.separator))
+ model.state.newCondaEnvName.set(model.projectPath.value.name)
}
- override fun getOrCreateSdk(): Sdk {
+ override fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Sdk {
return model.createCondaEnvironment(NewCondaEnvRequest.EmptyNamedEnv(pythonVersion.get(), model.state.newCondaEnvName.get()))
}
@@ -61,7 +69,7 @@ class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel)
false,
false,
false,
- //presenter.projectLocationContext is WslContext,
+ //presenter.projectLocationContext is WslContext,
false, // todo fix for wsl
InterpreterCreationMode.CUSTOM)
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/CustomNewEnvironmentCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/CustomNewEnvironmentCreator.kt
index f4fa9328af30..54e6265e9ba8 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/CustomNewEnvironmentCreator.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/CustomNewEnvironmentCreator.kt
@@ -17,7 +17,6 @@ import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
-import com.jetbrains.python.sdk.installPipIfNeeded
import com.jetbrains.python.statistics.InterpreterCreationMode
import com.jetbrains.python.statistics.InterpreterType
import org.jetbrains.annotations.ApiStatus.Internal
@@ -50,17 +49,21 @@ abstract class CustomNewEnvironmentCreator(private val name: String, model: Pyth
basePythonComboBox.setItems(model.baseInterpreters)
}
- override fun getOrCreateSdk(): Sdk {
+ override fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Sdk {
savePathToExecutableToProperties()
// todo think about better error handling
val selectedBasePython = model.state.baseInterpreter.get()!!
val homePath = model.installPythonIfNeeded(selectedBasePython)
- val newSdk = setupEnvSdk(null,
- null,
+ val module = when (moduleOrProject) {
+ is ModuleOrProject.ModuleAndProject -> moduleOrProject.module
+ is ModuleOrProject.ProjectOnly -> null
+ }
+ val newSdk = setupEnvSdk(moduleOrProject.project,
+ module,
model.baseSdks,
- model.projectPath.value,
+ model.projectPath.value.toString(),
homePath,
false)!!
SdkConfigurationUtil.addSdk(newSdk)
@@ -128,7 +131,7 @@ abstract class CustomNewEnvironmentCreator(private val name: String, model: Pyth
internal abstract fun savePathToExecutableToProperties()
- internal abstract fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List, projectPath: String, homePath: String?, installPackages: Boolean): Sdk?
+ protected abstract fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List, projectPath: String, homePath: String?, installPackages: Boolean): Sdk?
internal abstract fun detectExecutable()
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PyInterpreterModelParams.kt b/python/src/com/jetbrains/python/sdk/add/v2/PyInterpreterModelParams.kt
index 7533f6d93613..77d5fb0336ab 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PyInterpreterModelParams.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PyInterpreterModelParams.kt
@@ -4,6 +4,7 @@ package com.jetbrains.python.sdk.add.v2
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import org.jetbrains.annotations.ApiStatus
+import java.nio.file.Path
import kotlin.coroutines.CoroutineContext
/**
@@ -13,5 +14,5 @@ import kotlin.coroutines.CoroutineContext
data class PyInterpreterModelParams(
val scope: CoroutineScope,
val uiContext: CoroutineContext,
- val projectPathProperty: StateFlow? = null,
+ val projectPathProperty: StateFlow? = null,
)
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt
index 7a2da8873dcb..1f2ebb597397 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt
@@ -13,8 +13,10 @@ import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.add.v2.PythonSupportedEnvironmentManagers.*
+import kotlinx.coroutines.flow.StateFlow
+import java.nio.file.Path
-class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterModel, val moduleOrProject: ModuleOrProject? = null) {
+class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterModel, val moduleOrProject: ModuleOrProject? = null, private val projectPath: StateFlow? = null) {
private val propertyGraph = model.propertyGraph
private val selectionMethod = propertyGraph.property(PythonInterpreterSelectionMethod.CREATE_NEW)
@@ -25,7 +27,7 @@ class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterMod
private val newInterpreterCreators = mapOf(
VIRTUALENV to PythonNewVirtualenvCreator(model),
- CONDA to CondaNewEnvironmentCreator(model),
+ CONDA to CondaNewEnvironmentCreator(model, projectPath),
PIPENV to PipEnvNewEnvironmentCreator(model),
POETRY to PoetryNewEnvironmentCreator(model, moduleOrProject),
)
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt
index 905fe2198a0b..49ddff938be0 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterDialog.kt
@@ -26,7 +26,7 @@ class PythonAddLocalInterpreterDialog(private val dialogPresenter: PythonAddLoca
private lateinit var mainPanel: PythonAddCustomInterpreter
private lateinit var model: PythonLocalAddInterpreterModel
- private val basePath = dialogPresenter.pathForVEnv.toString()
+ private val basePath = dialogPresenter.pathForVEnv
init {
title = PyBundle.message("python.sdk.add.python.interpreter.title")
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterPresenter.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterPresenter.kt
index 9a06544aba65..7d3b01b7240d 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterPresenter.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddLocalInterpreterPresenter.kt
@@ -3,7 +3,6 @@ package com.jetbrains.python.sdk.add.v2
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.writeIntentReadAction
-import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.io.toNioPathOrNull
import com.jetbrains.python.sdk.ModuleOrProject
@@ -38,7 +37,7 @@ class PythonAddLocalInterpreterPresenter(val moduleOrProject: ModuleOrProject, v
val sdkCreatedFlow: Flow = _sdkShared.asSharedFlow()
suspend fun okClicked(addEnvironment: PythonAddEnvironment) {
- val sdk = withContext(Dispatchers.EDT) { writeIntentReadAction { addEnvironment.getOrCreateSdk () } }
+ val sdk = withContext(Dispatchers.EDT) { writeIntentReadAction { addEnvironment.getOrCreateSdk(moduleOrProject) } }
moduleOrProject.project.pySdkService.persistSdk(sdk)
_sdkShared.emit(sdk)
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddNewEnvironmentPanel.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddNewEnvironmentPanel.kt
index 7bc202228748..1e61a797c930 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddNewEnvironmentPanel.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddNewEnvironmentPanel.kt
@@ -9,7 +9,7 @@ import com.intellij.openapi.observable.properties.PropertyGraph
import com.intellij.openapi.observable.util.and
import com.intellij.openapi.observable.util.notEqualsTo
import com.intellij.openapi.observable.util.or
-import com.intellij.openapi.project.Project
+import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
import com.intellij.ui.dsl.builder.AlignX
@@ -19,21 +19,28 @@ import com.intellij.ui.dsl.builder.bindText
import com.intellij.util.ui.showingScope
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
+import com.jetbrains.python.sdk.ModuleOrProject
+import com.jetbrains.python.sdk.add.PySdkCreator
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMode.*
import com.jetbrains.python.statistics.InterpreterCreationMode
import com.jetbrains.python.statistics.InterpreterTarget
import com.jetbrains.python.statistics.InterpreterType
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
-import java.io.File
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import java.nio.file.Path
+
/**
* If `onlyAllowedInterpreterTypes` then only these types are displayed. All types displayed otherwise
*/
-class PythonAddNewEnvironmentPanel(val projectPath: StateFlow, onlyAllowedInterpreterTypes: Set? = null) {
+class PythonAddNewEnvironmentPanel(val projectPath: StateFlow, onlyAllowedInterpreterTypes: Set? = null) : PySdkCreator {
+
+ companion object {
+ private const val VENV_DIR = ".venv"
+ }
private val propertyGraph = PropertyGraph()
private val allowedInterpreterTypes = (onlyAllowedInterpreterTypes ?: PythonInterpreterSelectionMode.entries).also {
@@ -42,6 +49,8 @@ class PythonAddNewEnvironmentPanel(val projectPath: StateFlow, onlyAllow
}
}
+ private val initMutex = Mutex()
+
private var selectedMode = propertyGraph.property(this.allowedInterpreterTypes.first())
private var _projectVenv = propertyGraph.booleanProperty(selectedMode, PROJECT_VENV)
private var _baseConda = propertyGraph.booleanProperty(selectedMode, BASE_CONDA)
@@ -53,7 +62,7 @@ class PythonAddNewEnvironmentPanel(val projectPath: StateFlow, onlyAllow
private fun updateVenvLocationHint() {
val get = selectedMode.get()
- if (get == PROJECT_VENV) venvHint.set(message("sdk.create.simple.venv.hint", projectPath.value + File.separator))
+ if (get == PROJECT_VENV) venvHint.set(message("sdk.create.simple.venv.hint", projectPath.value.resolve(VENV_DIR).toString()))
else if (get == BASE_CONDA && PROJECT_VENV in allowedInterpreterTypes) venvHint.set(message("sdk.create.simple.conda.hint"))
}
@@ -63,15 +72,16 @@ class PythonAddNewEnvironmentPanel(val projectPath: StateFlow, onlyAllow
fun buildPanel(outerPanel: Panel) {
//presenter = PythonAddInterpreterPresenter(state, uiContext = Dispatchers.EDT + ModalityState.current().asContextElement())
model = PythonLocalAddInterpreterModel(PyInterpreterModelParams(service().coroutineScope,
- Dispatchers.EDT + ModalityState.current().asContextElement(), projectPath))
+ Dispatchers.EDT + ModalityState.current().asContextElement(), projectPath))
model.navigator.selectionMode = selectedMode
//presenter.controller = model
- custom = PythonAddCustomInterpreter(model)
+ custom = PythonAddCustomInterpreter(model, projectPath = projectPath)
val validationRequestor = WHEN_PROPERTY_CHANGED(selectedMode)
+
with(outerPanel) {
if (allowedInterpreterTypes.size > 1) { // No need to show control with only one selection
row(message("sdk.create.interpreter.type")) {
@@ -112,43 +122,46 @@ class PythonAddNewEnvironmentPanel(val projectPath: StateFlow, onlyAllow
custom.buildPanel(this, validationRequestor)
}.visibleIf(_custom)
}
-
selectedMode.afterChange { updateVenvLocationHint() }
}
+
fun onShown() {
- if (!initialized) {
- initialized = true
- val modalityState = ModalityState.current().asContextElement()
- model.scope.launch(Dispatchers.EDT + modalityState) {
- model.initialize()
- pythonBaseVersionComboBox.setItems(model.baseInterpreters)
- custom.onShown()
-
- updateVenvLocationHint()
+ val modalityState = ModalityState.current().asContextElement()
+ model.scope.launch(Dispatchers.EDT + modalityState) {
+ initMutex.withLock {
+ if (!initialized) {
+ model.initialize()
+ pythonBaseVersionComboBox.setItems(model.baseInterpreters)
+ custom.onShown()
+ updateVenvLocationHint()
+ model.navigator.restoreLastState(allowedInterpreterTypes)
+ initialized = true
+ }
}
-
- model.navigator.restoreLastState(allowedInterpreterTypes)
}
}
- fun getSdk(): Sdk {
+ @Deprecated("Use one with module or project")
+ fun getSdk(): Sdk = getSdk(ModuleOrProject.ProjectOnly(ProjectManager.getInstance().defaultProject))
+
+ override fun getSdk(moduleOrProject: ModuleOrProject): Sdk {
model.navigator.saveLastState()
return when (selectedMode.get()) {
PROJECT_VENV -> {
- val projectPath = Path.of(projectPath.value)
- model.setupVirtualenv(projectPath.resolve(".venv"), // todo just keep venv path, all the rest is in the model
+ val projectPath = projectPath.value
+ model.setupVirtualenv(projectPath.resolve(VENV_DIR), // todo just keep venv path, all the rest is in the model
projectPath,
//pythonBaseVersion.get()!!)
model.state.baseInterpreter.get()!!).getOrThrow()
}
BASE_CONDA -> model.selectCondaEnvironment(model.state.baseCondaEnv.get()!!.envIdentity)
- CUSTOM -> custom.currentSdkManager.getOrCreateSdk()
+ CUSTOM -> custom.currentSdkManager.getOrCreateSdk(moduleOrProject)
}
}
- fun createStatisticsInfo(): InterpreterStatisticsInfo = when (selectedMode.get()) {
+ override fun createStatisticsInfo(): InterpreterStatisticsInfo = when (selectedMode.get()) {
PROJECT_VENV -> InterpreterStatisticsInfo(InterpreterType.VIRTUALENV,
InterpreterTarget.LOCAL,
false,
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonExistingEnvironmentSelector.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonExistingEnvironmentSelector.kt
index 265f9657892a..1bb577304b00 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonExistingEnvironmentSelector.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonExistingEnvironmentSelector.kt
@@ -7,6 +7,7 @@ import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.Panel
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
+import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.statistics.InterpreterCreationMode
import com.jetbrains.python.statistics.InterpreterType
@@ -31,7 +32,7 @@ class PythonExistingEnvironmentSelector(model: PythonAddInterpreterModel) : Pyth
comboBox.setItems(model.allInterpreters)
}
- override fun getOrCreateSdk(): Sdk {
+ override fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Sdk {
// todo error handling, nullability issues
return setupSdkIfDetected(model.state.selectedInterpreter.get()!!, model.existingSdks)!!
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonNewVirtualenvCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonNewVirtualenvCreator.kt
index 131cc39f9667..3c50b6b5496f 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonNewVirtualenvCreator.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonNewVirtualenvCreator.kt
@@ -21,6 +21,7 @@ import com.intellij.util.ui.showingScope
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.newProject.collector.PythonNewProjectWizardCollector
+import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMethod.SELECT_EXISTING
import com.jetbrains.python.sdk.add.v2.PythonSupportedEnvironmentManagers.PYTHON
import com.jetbrains.python.statistics.InterpreterCreationMode
@@ -183,11 +184,9 @@ class PythonNewVirtualenvCreator(model: PythonMutableTargetAddInterpreterModel)
return currentName.removeSuffix(digitSuffix) + newSuffix
}
- override fun getOrCreateSdk(): Sdk {
+ override fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Sdk {
// todo remove project path, or move to controller
- val projectPath = model.projectPath.value
- assert(projectPath.isNotBlank()) { "Project path can't be blank" }
- return model.setupVirtualenv((Path.of(model.state.venvPath.get())), Path.of(projectPath), model.state.baseInterpreter.get()!!).getOrThrow()
+ return model.setupVirtualenv((Path.of(model.state.venvPath.get())), model.projectPath.value, model.state.baseInterpreter.get()!!).getOrThrow()
}
companion object {
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/common.kt b/python/src/com/jetbrains/python/sdk/add/v2/common.kt
index a404e097c686..17b81bf284be 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/common.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/common.kt
@@ -45,7 +45,7 @@ abstract class PythonAddEnvironment(open val model: PythonAddInterpreterModel) {
* Returns created SDK ready to use
*/
@RequiresEdt
- abstract fun getOrCreateSdk(): Sdk
+ abstract fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Sdk
abstract fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/models.kt b/python/src/com/jetbrains/python/sdk/add/v2/models.kt
index ef716431ccca..250efa08362f 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/models.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/models.kt
@@ -12,6 +12,7 @@ import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.observable.properties.PropertyGraph
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.io.FileUtil
+import com.intellij.util.SystemProperties
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
import com.jetbrains.python.newProject.steps.ProjectSpecificSettingsStep
import com.jetbrains.python.psi.LanguageLevel
@@ -36,7 +37,7 @@ abstract class PythonAddInterpreterModel(params: PyInterpreterModelParams) {
open val state = AddInterpreterState(propertyGraph)
open val targetEnvironmentConfiguration: TargetEnvironmentConfiguration? = null
- val projectPath = params.projectPathProperty ?: MutableStateFlow("") // todo how to populate?
+ val projectPath = params.projectPathProperty ?: MutableStateFlow(Path.of(SystemProperties.getUserHome())) // todo how to populate?
internal val scope = params.scope
internal val uiContext = params.uiContext
@@ -229,7 +230,7 @@ class PythonLocalAddInterpreterModel(params: PyInterpreterModelParams)
override fun suggestVenvPath(): String? {
// todo should this be a coroutine?
- return FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(projectPath.value))
+ return FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(projectPath.value.toString()))
}
}