mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
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:
committed by
intellij-monorepo-bot
parent
76960d644a
commit
7bd141a993
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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() ?
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
15
python/src/com/jetbrains/python/sdk/add/PySdkCreator.kt
Normal file
15
python/src/com/jetbrains/python/sdk/add/PySdkCreator.kt
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)!!
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user