diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.iml b/python/ide/impl/intellij.pycharm.community.ide.impl.iml index 02fa1aaaca61..dfacd42bab4a 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.iml +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.iml @@ -27,5 +27,6 @@ + \ No newline at end of file diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/djangoProject.kt b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/djangoProject.kt index 743281fb97c7..5a8a7d1e8919 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/djangoProject.kt +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/djangoProject.kt @@ -10,16 +10,14 @@ import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.PromoPages import com.intellij.openapi.util.NlsSafe import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle import com.jetbrains.python.icons.PythonIcons -import com.jetbrains.python.newProject.PyNewProjectSettings -import com.jetbrains.python.newProject.PythonProjectGenerator -import com.jetbrains.python.newProject.PythonPromoProjectGenerator +import com.jetbrains.python.newProject.promotion.PromoProjectGenerator import javax.swing.Icon import javax.swing.JPanel @NlsSafe private const val DJANGO_NAME = "Django" -internal class DjangoPromoProjectGenerator : PythonProjectGenerator(), PythonPromoProjectGenerator { +internal class DjangoPromoProjectGenerator : PromoProjectGenerator(isPython = true) { override fun getName(): String { return DJANGO_NAME } diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/javaScriptProject.kt b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/javaScriptProject.kt index cc90b430666b..aac0b40491c1 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/javaScriptProject.kt +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/javaScriptProject.kt @@ -3,25 +3,20 @@ package com.intellij.pycharm.community.ide.impl.promotion import com.intellij.icons.AllIcons import com.intellij.ide.wizard.withVisualPadding -import com.intellij.openapi.module.Module -import com.intellij.openapi.project.Project import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.FeaturePromoBundle import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.PluginAdvertiserService import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.PromoFeaturePage import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.PromoPages import com.intellij.openapi.util.NlsSafe -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.platform.DirectoryProjectGeneratorBase import com.jetbrains.python.icons.PythonIcons -import com.jetbrains.python.newProject.PyNewProjectSettings -import com.jetbrains.python.newProject.PythonPromoProjectGenerator +import com.jetbrains.python.newProject.promotion.PromoProjectGenerator import javax.swing.Icon import javax.swing.JPanel @NlsSafe private const val JAVASCRIPT_NAME = "JavaScript" -internal class JavaScriptPromoProjectGenerator : DirectoryProjectGeneratorBase(), 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> 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: - *

    - *
  1. Do not lean on settings at all. You simply implement {@link #configureProjectNoSettings(Project, VirtualFile, Module)} - * This way is common for project templates. - *
  2. - *
  3. 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
  4. - * - *
- *

- *

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())) } }