PyCharm: Rewrite NPW API: See com.jetbrains.python.newProjectWizard

This package (aka PyV3) fixes several issues, including
1. Lots of `DirectoryProjectGenerator` misuses: We now create UI and settings with `ProjectGeneratorPeer`: no more manuals casts in callback
2. Decouples project generation from UI: Settings aren't aware of UI but generate a project instead. Settings are mapped to the UI using Kotlin DSL UI
3. Kotlin DSL UI panel enables validation
4. `PySdkCreator` (an interface implemented by all "v2" SDK panels) now accepts `Module`, so we can provide it Poetry

The whole machinery of `PythonGenerateProjectCallback` (a pack of manual casts only mother could love) is completely deprecated and will be removed soon.

Lots of small changes towards `suspend` functions, Kotlin DSL UI and `Flow` decrease the technical debt.


Merge-request: IJ-MR-144503
Merged-by: Ilya Kazakevich <ilya.kazakevich@jetbrains.com>

GitOrigin-RevId: 21963e843b0ae67c71a6fc5ea1229cb0f57915a9
This commit is contained in:
Ilya Kazakevich
2024-09-10 19:50:12 +00:00
committed by intellij-monorepo-bot
parent 76960d644a
commit 7bd141a993
50 changed files with 678 additions and 316 deletions

View File

@@ -27,5 +27,6 @@
<orderEntry type="library" name="http-client" level="project" />
<orderEntry type="module" module-name="intellij.platform.whatsNew" />
<orderEntry type="module" module-name="intellij.pycharm.community.ide.impl.promotion" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
</component>
</module>

View File

@@ -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<PyNewProjectSettings>(), PythonPromoProjectGenerator {
internal class DjangoPromoProjectGenerator : PromoProjectGenerator(isPython = true) {
override fun getName(): String {
return DJANGO_NAME
}

View File

@@ -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<PyNewProjectSettings>(), PythonPromoProjectGenerator {
internal class JavaScriptPromoProjectGenerator : PromoProjectGenerator(isPython = false) {
override fun getName(): String {
return JAVASCRIPT_NAME
}
@@ -30,9 +25,6 @@ internal class JavaScriptPromoProjectGenerator : DirectoryProjectGeneratorBase<P
return AllIcons.Ultimate.PycharmLock
}
override fun generateProject(project: Project, baseDir: VirtualFile, settings: PyNewProjectSettings, module: Module) {
// just a promo generator, does not generate anything
}
override fun createPromoPanel(): JPanel {
return PromoPages.buildWithTryUltimate(

View File

@@ -104,7 +104,7 @@
<group id="WelcomeScreen.Platform.NewProject">
<group id="WelcomeScreen.CreateDirectoryProject"
class="com.intellij.pycharm.community.ide.impl.newProject.steps.PyCharmNewProjectStep"/>
class="com.intellij.pycharm.community.ide.impl.newProject.impl.PyV3NewProjectStepAction"/>
<reference ref="WelcomeScreen.OpenDirectoryProject"/>
<add-to-group group-id="WelcomeScreen.QuickStart" anchor="first"/>

View File

@@ -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) {

View File

@@ -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

View File

@@ -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<PyV3BaseProjectSettings>(PyV3Customization) {
private object PyV3Customization : Customization<PyV3BaseProjectSettings>() {
override fun createEmptyProjectGenerator(): DirectoryProjectGenerator<PyV3BaseProjectSettings> = PyV3EmptyProjectGenerator()
override fun createProjectSpecificSettingsStep(
projectGenerator: DirectoryProjectGenerator<PyV3BaseProjectSettings>,
callback: AbstractCallback<PyV3BaseProjectSettings>,
): ProjectSettingsStepBase<PyV3BaseProjectSettings> =
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<DirectoryProjectGenerator<*>>,
callback: AbstractCallback<PyV3BaseProjectSettings>,
): Array<out AnAction> {
// 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<AnAction>(
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<PyV3BaseProjectSettings> {
return PythonProjectSpecificSettingsStep(projectGenerator as PythonProjectGenerator<PyNewProjectSettings>, PythonGenerateProjectCallback()) as ProjectSettingsStepBase<PyV3BaseProjectSettings>
}
}
}

View File

@@ -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<PyV3BaseProjectSettings>,
) : ProjectSettingsStepBase<PyV3BaseProjectSettings>(generator, callback)

View File

@@ -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>(
PyV3EmptyProjectSettings(generateWelcomeScript = false), PyV3EmptyProjectUI) {
override fun getName(): @Nls String = PyBundle.message("pure.python.project")
override fun getLogo(): Icon = PythonPsiApiIcons.Python
}

View File

@@ -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)"
}
}

View File

@@ -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<PyV3EmptyProjectSettings> {
override fun configureUpperPanel(settings: PyV3EmptyProjectSettings, checkBoxRow: Row, belowCheckBoxes: Panel) {
checkBoxRow.checkBox(PyBundle.message("new.project.welcome")).bindSelected(settings::generateWelcomeScript)
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<PyNewProjectSettings> {
public PyCharmNewProjectStep() {
super(new Customization());
}
protected static class Customization extends AbstractNewProjectStep.Customization<PyNewProjectSettings> {
@NotNull
@Override
protected AbstractCallback<PyNewProjectSettings> createCallback() {
return new PythonGenerateProjectCallback<>();
}
@NotNull
@Override
protected DirectoryProjectGenerator<PyNewProjectSettings> createEmptyProjectGenerator() {
return new PythonBaseProjectGenerator();
}
@NotNull
@Override
protected ProjectSettingsStepBase<PyNewProjectSettings> createProjectSpecificSettingsStep(@NotNull DirectoryProjectGenerator<PyNewProjectSettings> projectGenerator,
@NotNull AbstractCallback<PyNewProjectSettings> callback) {
var npwGenerator = ObjectUtils.tryCast(projectGenerator, NewProjectWizardDirectoryGeneratorAdapter.class);
if (npwGenerator != null) {
//noinspection unchecked
return new NewProjectWizardProjectSettingsStep<PyNewProjectSettings>(npwGenerator);
}
else {
return new PythonProjectSpecificSettingsStep<>(projectGenerator, callback);
}
}
@Override
public AnAction[] getActions(@NotNull List<? extends DirectoryProjectGenerator<?>> generators,
@NotNull AbstractCallback<PyNewProjectSettings> 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<PyNewProjectSettings>)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};
}
}
}

View File

@@ -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<PyNewProjectSettings?>(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
}

View File

@@ -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<T extends PyNewProjectSettings> extends AbstractNewProjectStep.AbstractCallback<T> {
@Override
@@ -37,11 +27,6 @@ public final class PythonGenerateProjectCallback<T extends PyNewProjectSettings>
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<T extends PyNewProjectSettings>
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> 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<T extends PyNewProjectSettings>
@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<T extends PyNewProjectSettings>
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;

View File

@@ -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

View File

@@ -151,5 +151,6 @@
<orderEntry type="module" module-name="intellij.platform.ui.jcef" />
<orderEntry type="module" module-name="intellij.libraries.ktor.client" />
<orderEntry type="module" module-name="intellij.libraries.ktor.client.cio" />
<orderEntry type="module" module-name="intellij.platform.util.progress" />
</component>
</module>

View File

@@ -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 <code>main.py</code> 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;
}
}

View File

@@ -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
python.toolwindow.packages.collapse.all.action=Collapse All
django.template.language=Template Language

View File

@@ -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)
* <br/>
* If your project does not support remote projects generation, be sure to set flag in ctor:{@link #PythonProjectGenerator(boolean)}
* <br/>
* <h2>Module vs PyCharm projects</h2>
* <p>
* 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:
* <ol>
* <li>Do not lean on settings at all. You simply implement {@link #configureProjectNoSettings(Project, VirtualFile, Module)}
* This way is common for project templates.
* </li>
* <li>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</li>
* </li>
* </ol>
* </p>
* <h2>How to report framework installation failures</h2>
* <p>{@link PyNewProjectSettings#getSdk()} may return null, or something else may prevent package installation.
* Use {@link #reportPackageInstallationFailure(String, Pair)} in this case.
* </p>
*
* @param <T> project settings
* @deprecated Use {@link com.jetbrains.python.newProjectWizard}
*/
@Deprecated
public abstract class PythonProjectGenerator<T extends PyNewProjectSettings> extends DirectoryProjectGeneratorBase<T> {
public static final PyNewProjectSettings NO_SETTINGS = new PyNewProjectSettings();
private static final Logger LOGGER = Logger.getInstance(PythonProjectGenerator.class);

View File

@@ -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
}

View File

@@ -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<PyV3BaseProjectSettings> {
abstract fun createPromoPanel(): JPanel
override fun generateProject(project: Project, baseDir: VirtualFile, settings: PyV3BaseProjectSettings, module: Module) = Unit
override fun validate(baseDirPath: String): ValidationResult = ValidationResult.OK
}

View File

@@ -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<PyV3BaseProjectSettings>(generator, AbstractNewProjectStep.AbstractCallback()) {
override fun createBasePanel(): JPanel {
myCreateButton.isEnabled = false
return generator.createPromoPanel()
}
}

View File

@@ -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;

View File

@@ -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<T extends PyNewProjectSettings> extends ProjectSettingsStepBase<T> implements DumbAware {
private boolean myInstallFramework;
private @Nullable PyAddSdkGroupPanel myInterpreterPanel;
@@ -181,7 +185,7 @@ public class ProjectSpecificSettingsStep<T extends PyNewProjectSettings> 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<T extends PyNewProjectSettings> 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() ?

View File

@@ -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<T : PyNewProjectSettings>(
projectGenerator: DirectoryProjectGenerator<T>,
callback: AbstractNewProjectStep.AbstractCallback<T>,
)
: ProjectSpecificSettingsStep<T>(projectGenerator, callback), DumbAware {
) : ProjectSpecificSettingsStep<T>(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<T : PyNewProjectSettings>(
init {
projectLocation.afterChange {
projectLocationFlow.value = projectLocation.get()
projectLocationFlow.value = Path.of(projectLocation.get())
}
}
@@ -103,7 +108,7 @@ class PythonProjectSpecificSettingsStep<T : PyNewProjectSettings>(
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<T : PyNewProjectSettings>(
}
override fun getSdk(): Sdk {
// It is here only for DS, not used in PyCharm
return PyLazySdk("Uninitialized environment") { interpreterPanel?.getSdk() }
}

View File

@@ -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<Path, @NlsSafe String> {
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)
}

View File

@@ -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)"
}
}

View File

@@ -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<TYPE_SPECIFIC_SETTINGS : PyV3ProjectTypeSpecificSettings>(
private val typeSpecificSettings: TYPE_SPECIFIC_SETTINGS,
private val typeSpecificUI: PyV3ProjectTypeSpecificUI<TYPE_SPECIFIC_SETTINGS>?,
private val allowedInterpreterTypes: Set<PythonInterpreterSelectionMode>? = null,
) : DirectoryProjectGenerator<PyV3BaseProjectSettings> {
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<MyService>().coroutineScope
coroutineScope.launch {
typeSpecificSettings.generateProject(module, baseDir, settings.generateAndGetSdk(module, baseDir))
}
}
override fun createPeer(): ProjectGeneratorPeer<PyV3BaseProjectSettings> =
PyV3GeneratorPeer(baseSettings, projectPathFlow, typeSpecificUI?.let { Pair(it, typeSpecificSettings) }, allowedInterpreterTypes)
override fun validate(baseDirPath: String): ValidationResult =
when (val pathOrError = validateProjectPathAndGetPath(baseDirPath)) {
is Result.Success<Path, *> -> {
projectPathFlow.value = pathOrError.result
ValidationResult.OK
}
is Result.Failure<*, @Nls String> -> ValidationResult(pathOrError.error)
}
@Service(Service.Level.PROJECT)
private class MyService(val coroutineScope: CoroutineScope)
}

View File

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

View File

@@ -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<PROJECT_SPECIFIC_SETTINGS : PyV3ProjectTypeSpecificSettings> {
/**
* 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
}

View File

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

View File

@@ -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<TYPE_SPECIFIC_SETTINGS : PyV3ProjectTypeSpecificSettings> @RequiresEdt constructor(
baseSettings: PyV3BaseProjectSettings,
projectPath: StateFlow<Path>,
specificUiAndSettings: Pair<PyV3ProjectTypeSpecificUI<TYPE_SPECIFIC_SETTINGS>, TYPE_SPECIFIC_SETTINGS>?,
allowedInterpreterTypes: Set<PythonInterpreterSelectionMode>? = 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
}
}

View File

@@ -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<TYPE_SPECIFIC_SETTINGS : PyV3ProjectTypeSpecificSettings>(
baseSettings: PyV3BaseProjectSettings,
projectPath: StateFlow<Path>,
specificUiAndSettings: Pair<PyV3ProjectTypeSpecificUI<TYPE_SPECIFIC_SETTINGS>, TYPE_SPECIFIC_SETTINGS>?,
allowedInterpreterTypes:Set<PythonInterpreterSelectionMode>?
) : ProjectGeneratorPeer<PyV3BaseProjectSettings> {
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
}

View File

@@ -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;

View File

@@ -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:
* <p/>
* {@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).
* <p/>
* {@link com.jetbrains.python.newProjectWizard.PyV3ProjectTypeSpecificUI} is a class that binds settings, mentioned above, to the Kotlin DSL UI panel.
* <p/>
* {@link com.jetbrains.python.newProjectWizard.PyV3ProjectBaseGenerator} connects these two and must be registered as EP.
*
*
*/
package com.jetbrains.python.newProjectWizard;

View File

@@ -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
}

View File

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

View File

@@ -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<Path>?) : PythonNewEnvironmentCreator(model) {
private lateinit var pythonVersion: ObservableMutableProperty<LanguageLevel>
private lateinit var versionComboBox: ComboBox<LanguageLevel>
@@ -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)
}

View File

@@ -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<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Sdk?
protected abstract fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): Sdk?
internal abstract fun detectExecutable()
}

View File

@@ -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<String>? = null,
val projectPathProperty: StateFlow<Path>? = null,
)

View File

@@ -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<Path>? = 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),
)

View File

@@ -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")

View File

@@ -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<Sdk> = _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)
}

View File

@@ -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<String>, onlyAllowedInterpreterTypes: Set<PythonInterpreterSelectionMode>? = null) {
class PythonAddNewEnvironmentPanel(val projectPath: StateFlow<Path>, onlyAllowedInterpreterTypes: Set<PythonInterpreterSelectionMode>? = 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<String>, 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<String>, 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<String>, onlyAllow
fun buildPanel(outerPanel: Panel) {
//presenter = PythonAddInterpreterPresenter(state, uiContext = Dispatchers.EDT + ModalityState.current().asContextElement())
model = PythonLocalAddInterpreterModel(PyInterpreterModelParams(service<PythonAddSdkService>().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<String>, 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,

View File

@@ -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)!!
}

View File

@@ -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 {

View File

@@ -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
}

View File

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