From a65a3f894cb057022776438c4fc45524febcf353 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 7 May 2018 18:52:11 +0300 Subject: [PATCH] PY-9081 PY-12247 PY-26492 Associate interpreters with modules, not with projects --- .../python/PythonSdkConfigurator.java | 2 +- .../python/configuration/EditSdkDialog.java | 2 +- .../PyActiveSdkConfigurable.java | 4 +- .../PyConfigurableInterpreterList.java | 2 +- .../configuration/PythonSdkDetailsDialog.java | 13 ++-- .../steps/ProjectSpecificSettingsStep.java | 4 +- .../newProject/steps/PyAddExistingSdkPanel.kt | 4 +- .../steps/PyAddNewEnvironmentPanel.kt | 6 +- .../steps/PythonGenerateProjectCallback.java | 4 +- .../steps/PythonSdkChooserCombo.java | 8 +- .../src/com/jetbrains/python/sdk/PySdkExt.kt | 57 ++++++++------ .../python/sdk/PythonSdkAdditionalData.java | 31 ++++---- .../python/sdk/PythonSdkDetailsStep.java | 11 ++- .../jetbrains/python/sdk/PythonSdkType.java | 2 +- .../sdk/add/PyAddExistingCondaEnvPanel.kt | 8 +- .../sdk/add/PyAddExistingVirtualEnvPanel.kt | 8 +- .../python/sdk/add/PyAddNewCondaEnvPanel.kt | 11 ++- .../python/sdk/add/PyAddNewVirtualEnvPanel.kt | 8 +- .../python/sdk/add/PyAddPipEnvPanel.kt | 78 ++++++++++++------- .../python/sdk/add/PyAddSdkDialog.kt | 34 ++++---- .../python/sdk/add/PyAddSdkProvider.kt | 3 +- .../sdk/flavors/VirtualEnvSdkFlavor.java | 13 +++- .../jetbrains/python/sdk/flavors/pipenv.kt | 9 +-- 23 files changed, 193 insertions(+), 129 deletions(-) diff --git a/python/ide/src/com/jetbrains/python/PythonSdkConfigurator.java b/python/ide/src/com/jetbrains/python/PythonSdkConfigurator.java index 35986da08b03..c9ecab6a5805 100644 --- a/python/ide/src/com/jetbrains/python/PythonSdkConfigurator.java +++ b/python/ide/src/com/jetbrains/python/PythonSdkConfigurator.java @@ -75,7 +75,7 @@ public class PythonSdkConfigurator implements DirectoryProjectConfigurator { additionalData = new PythonSdkAdditionalData(PythonSdkFlavor.getFlavor(virtualEnvSdk.getHomePath())); ((ProjectJdkImpl)virtualEnvSdk).setSdkAdditionalData(additionalData); } - ((PythonSdkAdditionalData) additionalData).associateWithProject(project); + ((PythonSdkAdditionalData) additionalData).setAssociatedModulePath(project.getBasePath()); return; } return; diff --git a/python/python-community-configure/src/com/jetbrains/python/configuration/EditSdkDialog.java b/python/python-community-configure/src/com/jetbrains/python/configuration/EditSdkDialog.java index 1703584234e1..0da822de92a4 100644 --- a/python/python-community-configure/src/com/jetbrains/python/configuration/EditSdkDialog.java +++ b/python/python-community-configure/src/com/jetbrains/python/configuration/EditSdkDialog.java @@ -70,7 +70,7 @@ public class EditSdkDialog extends DialogWrapper { if ((sdkFlavor instanceof VirtualEnvSdkFlavor) || (sdkFlavor instanceof CondaEnvSdkFlavor)) { PythonSdkAdditionalData data = (PythonSdkAdditionalData) sdk.getSdkAdditionalData(); if (data != null) { - final String path = data.getAssociatedProjectPath(); + final String path = data.getAssociatedModulePath(); if (path != null) { myAssociateCheckbox.setSelected(true); final String basePath = project.getBasePath(); diff --git a/python/python-community-configure/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java b/python/python-community-configure/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java index 482f369a5edd..38c692379a26 100644 --- a/python/python-community-configure/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java +++ b/python/python-community-configure/src/com/jetbrains/python/configuration/PyActiveSdkConfigurable.java @@ -214,7 +214,7 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable { ? new PythonSdkDetailsDialog(myProject, myAddSdkCallback, getSettingsModifiedCallback()) : new PythonSdkDetailsDialog(myModule, myAddSdkCallback, getSettingsModifiedCallback()); - PythonSdkDetailsStep.show(myProject, myProjectSdksModel.getSdks(), allDialog, myMainPanel, + PythonSdkDetailsStep.show(myProject, myModule, myProjectSdksModel.getSdks(), allDialog, myMainPanel, myDetailsButton.getLocationOnScreen(), null, myAddSdkCallback); } @@ -306,7 +306,7 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable { final List allPythonSdks = myInterpreterList.getAllPythonSdks(myProject); final List visibleSdks = StreamEx .of(allPythonSdks) - .filter(sdk -> !PythonSdkType.isInvalid(sdk) && !PySdkExtKt.isAssociatedWithAnotherProject(sdk, myProject)) + .filter(sdk -> !PythonSdkType.isInvalid(sdk) && !PySdkExtKt.isAssociatedWithAnotherModule(sdk, myModule)) .toList(); final LinkedHashSet virtualEnvironments = StreamEx .of(visibleSdks) diff --git a/python/python-community-configure/src/com/jetbrains/python/configuration/PyConfigurableInterpreterList.java b/python/python-community-configure/src/com/jetbrains/python/configuration/PyConfigurableInterpreterList.java index c06daf8d1ee8..27e329f974e2 100644 --- a/python/python-community-configure/src/com/jetbrains/python/configuration/PyConfigurableInterpreterList.java +++ b/python/python-community-configure/src/com/jetbrains/python/configuration/PyConfigurableInterpreterList.java @@ -135,7 +135,7 @@ public class PyConfigurableInterpreterList { private static boolean associatedWithCurrent(Sdk o1, Project project) { final PythonSdkAdditionalData data = (PythonSdkAdditionalData)o1.getSdkAdditionalData(); if (data != null) { - final String path = data.getAssociatedProjectPath(); + final String path = data.getAssociatedModulePath(); final String projectBasePath = project.getBasePath(); if (path != null && path.equals(projectBasePath)) { return true; diff --git a/python/python-community-configure/src/com/jetbrains/python/configuration/PythonSdkDetailsDialog.java b/python/python-community-configure/src/com/jetbrains/python/configuration/PythonSdkDetailsDialog.java index 9a503e6d127e..1add83a8be94 100644 --- a/python/python-community-configure/src/com/jetbrains/python/configuration/PythonSdkDetailsDialog.java +++ b/python/python-community-configure/src/com/jetbrains/python/configuration/PythonSdkDetailsDialog.java @@ -230,7 +230,7 @@ public class PythonSdkDetailsDialog extends DialogWrapper { Sdk projectSdk = getSdk(); final List notAssociatedWithOtherProjects = StreamEx .of(allPythonSdks) - .filter(sdk -> !PySdkExtKt.isAssociatedWithAnotherProject(sdk, myProject)) + .filter(sdk -> !PySdkExtKt.isAssociatedWithAnotherModule(sdk, myModule)) .toList(); final List pythonSdks = myHideOtherProjectVirtualenvs ? notAssociatedWithOtherProjects : allPythonSdks; @@ -256,7 +256,7 @@ public class PythonSdkDetailsDialog extends DialogWrapper { private void addSdk(AnActionButton button) { PythonSdkDetailsStep - .show(myProject, myProjectSdksModel.getSdks(), null, myMainPanel, button.getPreferredPopupPoint().getScreenPoint(), + .show(myProject, myModule, myProjectSdksModel.getSdks(), null, myMainPanel, button.getPreferredPopupPoint().getScreenPoint(), null, sdk -> addCreatedSdk(sdk, true)); } @@ -339,11 +339,14 @@ public class PythonSdkDetailsDialog extends DialogWrapper { additionalData = new PythonSdkAdditionalData(PythonSdkFlavor.getFlavor(modificator.getHomePath())); modificator.setSdkAdditionalData(additionalData); } - if (isAssociated && myProject != null) { - additionalData.associateWithProject(myProject); + if (isAssociated && myModule != null) { + additionalData.associateWithModule(myModule); + } + else if (isAssociated && myProject != null) { + additionalData.setAssociatedModulePath(myProject.getBasePath()); } else { - additionalData.setAssociatedProjectPath(null); + additionalData.resetAssociatedModulePath(); } } diff --git a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java index 8a0d56f960ef..898ce27bcaab 100644 --- a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java +++ b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/ProjectSpecificSettingsStep.java @@ -283,7 +283,7 @@ public class ProjectSpecificSettingsStep extends ProjectSettingsStepBase i final String newProjectPath = getNewProjectPath(); final PyAddNewEnvironmentPanel newEnvironmentPanel = new PyAddNewEnvironmentPanel(existingSdks, newProjectPath, preferredEnvironment); - final PyAddExistingSdkPanel existingSdkPanel = new PyAddExistingSdkPanel(null, existingSdks, newProjectPath, preferredSdk); + final PyAddExistingSdkPanel existingSdkPanel = new PyAddExistingSdkPanel(null, null, existingSdks, newProjectPath, preferredSdk); final PyAddSdkPanel defaultPanel = PySdkSettings.getInstance().getUseNewEnvironmentForNewProject() ? newEnvironmentPanel : existingSdkPanel; @@ -357,7 +357,7 @@ public class ProjectSpecificSettingsStep extends ProjectSettingsStepBase i final List pythonSdks = PyConfigurableInterpreterList.getInstance(null).getAllPythonSdks(); Iterables.removeIf(pythonSdks, sdk -> !(sdk.getSdkType() instanceof PythonSdkType) || PythonSdkType.isInvalid(sdk) || - PySdkExtKt.getAssociatedProjectPath(sdk) != null); + PySdkExtKt.getAssociatedModulePath(sdk) != null); Collections.sort(pythonSdks, new PreferredSdkComparator()); return pythonSdks; } diff --git a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddExistingSdkPanel.kt b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddExistingSdkPanel.kt index 9378f9ff56ee..3c7029e6085a 100644 --- a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddExistingSdkPanel.kt +++ b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddExistingSdkPanel.kt @@ -3,6 +3,7 @@ package com.jetbrains.python.newProject.steps import com.intellij.execution.ExecutionException import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.ui.DialogWrapper @@ -26,6 +27,7 @@ import java.awt.Component * @author vlan */ class PyAddExistingSdkPanel(project: Project?, + module: Module?, existingSdks: List, newProjectPath: String?, preferredSdk: Sdk?) : PyAddSdkPanel() { @@ -55,7 +57,7 @@ class PyAddExistingSdkPanel(project: Project?, init { layout = BorderLayout() - sdkChooserCombo = PythonSdkChooserCombo(project, existingSdks, newProjectPath, { it != null && it == preferredSdk }).apply { + sdkChooserCombo = PythonSdkChooserCombo(project, module, existingSdks, newProjectPath, { it != null && it == preferredSdk }).apply { if (SystemInfo.isMac && !UIUtil.isUnderDarcula()) { putClientProperty("JButton.buttonType", null) } diff --git a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddNewEnvironmentPanel.kt b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddNewEnvironmentPanel.kt index 50888ba9d90c..b7e57e322087 100644 --- a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddNewEnvironmentPanel.kt +++ b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PyAddNewEnvironmentPanel.kt @@ -27,9 +27,9 @@ class PyAddNewEnvironmentPanel(existingSdks: List, newProjectPath: String?, } } - private val panels = listOf(PyAddNewVirtualEnvPanel(null, existingSdks, newProjectPath), - PyAddPipEnvPanel(null, existingSdks, newProjectPath), - PyAddNewCondaEnvPanel(null, existingSdks, newProjectPath)) + private val panels = listOf(PyAddNewVirtualEnvPanel(null, null, existingSdks, newProjectPath), + PyAddPipEnvPanel(null, null, existingSdks, newProjectPath), + PyAddNewCondaEnvPanel(null, null, existingSdks, newProjectPath)) var selectedPanel: PyAddNewEnvPanel = panels.find { it.envName == preferredType ?: PySdkSettings.instance.preferredEnvironmentType } ?: panels[0] diff --git a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PythonGenerateProjectCallback.java b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PythonGenerateProjectCallback.java index 7bf653ea0bf5..75e026f73c41 100644 --- a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PythonGenerateProjectCallback.java +++ b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PythonGenerateProjectCallback.java @@ -75,11 +75,9 @@ public class PythonGenerateProjectCallback extends AbstractNewProjectStep.Abs for (Sdk s : sdks) { final SdkAdditionalData additionalData = s.getSdkAdditionalData(); if (additionalData instanceof PythonSdkAdditionalData) { - ((PythonSdkAdditionalData)additionalData).reassociateWithCreatedProject(newProject); + ((PythonSdkAdditionalData)additionalData).reAssociateWithCreatedProject(newProject); } } - } - if (generator instanceof PythonProjectGenerator) { ((PythonProjectGenerator)generator).afterProjectGenerated(newProject); } } diff --git a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PythonSdkChooserCombo.java b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PythonSdkChooserCombo.java index d8d804eec66f..545cd30acfc5 100644 --- a/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PythonSdkChooserCombo.java +++ b/python/python-community-configure/src/com/jetbrains/python/newProject/steps/PythonSdkChooserCombo.java @@ -16,6 +16,7 @@ package com.jetbrains.python.newProject.steps; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; @@ -47,6 +48,7 @@ public class PythonSdkChooserCombo extends ComboboxWithBrowseButton { @SuppressWarnings("unchecked") public PythonSdkChooserCombo(@Nullable final Project project, + @Nullable final Module module, @NotNull List sdks, @Nullable String newProjectPath, @NotNull final Condition acceptableSdkCondition) { @@ -58,7 +60,7 @@ public class PythonSdkChooserCombo extends ComboboxWithBrowseButton { comboBox.setRenderer(new PySdkListCellRenderer(null)); addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - showOptions(project); + showOptions(project, module); notifyChanged(e); } }); @@ -77,13 +79,13 @@ public class PythonSdkChooserCombo extends ComboboxWithBrowseButton { getComboBox().setToolTipText(item instanceof Sdk ? ((Sdk)item).getHomePath() : null); } - private void showOptions(@Nullable final Project project) { + private void showOptions(@Nullable final Project project, @Nullable Module module) { final PyConfigurableInterpreterList interpreterList = PyConfigurableInterpreterList.getInstance(project); final Sdk[] sdks = interpreterList.getModel().getSdks(); //noinspection unchecked final JComboBox comboBox = getComboBox(); final Sdk oldSelectedSdk = (Sdk)comboBox.getSelectedItem(); - PythonSdkDetailsStep.show(project, sdks, null, this, getButton().getLocationOnScreen(), myNewProjectPath, sdk -> { + PythonSdkDetailsStep.show(project, module, sdks, null, this, getButton().getLocationOnScreen(), myNewProjectPath, sdk -> { if (sdk == null) return; final ProjectSdksModel projectSdksModel = interpreterList.getModel(); if (projectSdksModel.findSdk(sdk) == null) { diff --git a/python/src/com/jetbrains/python/sdk/PySdkExt.kt b/python/src/com/jetbrains/python/sdk/PySdkExt.kt index a7e8c4591197..5375c4b062f7 100644 --- a/python/src/com/jetbrains/python/sdk/PySdkExt.kt +++ b/python/src/com/jetbrains/python/sdk/PySdkExt.kt @@ -21,12 +21,14 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project +import com.intellij.openapi.project.rootManager import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil import com.intellij.openapi.roots.ModuleRootModificationUtil import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.StandardFileSystems +import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.PathUtil import com.intellij.webcore.packaging.PackagesNotificationPanel import com.jetbrains.python.packaging.ui.PyPackageManagementService @@ -61,11 +63,11 @@ fun detectSystemWideSdks(existingSdks: List): List { .toList() } -fun detectVirtualEnvs(project: Project?, existingSdks: List): List = - filterSuggestedPaths(VirtualEnvSdkFlavor.INSTANCE.suggestHomePaths(), existingSdks, project) +fun detectVirtualEnvs(module: Module?, existingSdks: List): List = + filterSuggestedPaths(VirtualEnvSdkFlavor.INSTANCE.suggestHomePaths(), existingSdks, module) -fun detectCondaEnvs(project: Project?, existingSdks: List): List = - filterSuggestedPaths(CondaEnvSdkFlavor.INSTANCE.suggestHomePaths(), existingSdks, project) +fun detectCondaEnvs(module: Module?, existingSdks: List): List = + filterSuggestedPaths(CondaEnvSdkFlavor.INSTANCE.suggestHomePaths(), existingSdks, module) fun createSdkByGenerateTask(generateSdkHomePath: Task.WithResult, existingSdks: List, @@ -88,30 +90,30 @@ fun createSdkByGenerateTask(generateSdkHomePath: Task.WithResult associateWithNewProject() - project != null -> associateWithProject(project) + module != null -> associateWithModule(module) } } } -fun Sdk.isAssociatedWithProject(project: Project?): Boolean { - val basePath = project?.basePath - val associatedPath = associatedProjectPath +fun Sdk.isAssociatedWithModule(module: Module?): Boolean { + val basePath = module?.basePath + val associatedPath = associatedModulePath if (basePath != null && associatedPath == basePath) return true - if (isAssociatedWithAnotherProject(project)) return false - return isLocatedInsideProject(project) || containsProjectName(project) + if (isAssociatedWithAnotherModule(module)) return false + return isLocatedInsideModule(module) || containsModuleName(module) } -fun Sdk.isAssociatedWithAnotherProject(project: Project?): Boolean { - val basePath = project?.basePath ?: return false - val associatedPath = associatedProjectPath ?: return false +fun Sdk.isAssociatedWithAnotherModule(module: Module?): Boolean { + val basePath = module?.basePath ?: return false + val associatedPath = associatedModulePath ?: return false return basePath != associatedPath } -val Sdk.associatedProjectPath: String? +val Sdk.associatedModulePath: String? // TODO: Support .project associations get() = associatedPathFromAdditionalData /*?: associatedPathFromDotProject*/ @@ -125,9 +127,9 @@ fun PyDetectedSdk.setup(existingSdks: List): Sdk? { return SdkConfigurationUtil.setupSdk(existingSdks.toTypedArray(), homeDir, PythonSdkType.getInstance(), false, null, null) } -fun PyDetectedSdk.setupAssociated(existingSdks: List, associatedProjectPath: String?): Sdk? { +fun PyDetectedSdk.setupAssociated(existingSdks: List, associatedModulePath: String?): Sdk? { val homeDir = homeDirectory ?: return null - val suggestedName = homePath?.let { suggestAssociatedSdkName(it, associatedProjectPath) } + val suggestedName = homePath?.let { suggestAssociatedSdkName(it, associatedModulePath) } return SdkConfigurationUtil.setupSdk(existingSdks.toTypedArray(), homeDir, PythonSdkType.getInstance(), false, null, suggestedName) } @@ -149,6 +151,13 @@ var Project.pythonSdk: Sdk? } } +val Module.baseDir: VirtualFile? + get() = rootManager.contentRoots.firstOrNull() + +val Module.basePath: String? + get() = baseDir?.path + + private fun suggestAssociatedSdkName(sdkHome: String, associatedPath: String?): String? { val baseSdkName = PythonSdkType.suggestBaseSdkName(sdkHome) ?: return null val venvRoot = PythonSdkType.getVirtualEnvRoot(sdkHome)?.path @@ -186,11 +195,11 @@ private val Sdk.associatedPathFromDotProject: String? } private val Sdk.associatedPathFromAdditionalData: String? - get() = (sdkAdditionalData as? PythonSdkAdditionalData)?.associatedProjectPath + get() = (sdkAdditionalData as? PythonSdkAdditionalData)?.associatedModulePath -private fun Sdk.isLocatedInsideProject(project: Project?): Boolean { +private fun Sdk.isLocatedInsideModule(module: Module?): Boolean { val homePath = homePath ?: return false - val basePath = project?.basePath ?: return false + val basePath = module?.basePath ?: return false return FileUtil.isAncestor(basePath, homePath, true) } @@ -202,9 +211,9 @@ private val PyDetectedSdk.guessedLanguageLevel: LanguageLevel? return LanguageLevel.fromPythonVersion(versionString) } -private fun Sdk.containsProjectName(project: Project?): Boolean { +private fun Sdk.containsModuleName(module: Module?): Boolean { val path = homePath ?: return false - val name = project?.name ?: return false + val name = module?.name ?: return false return path.contains(name, true) } @@ -220,14 +229,14 @@ fun Sdk.getOrCreateAdditionalData(): PythonSdkAdditionalData { private fun filterSuggestedPaths(suggestedPaths: MutableCollection, existingSdks: List, - project: Project?): List { + module: Module?): List { val existingPaths = existingSdks.map { it.homePath }.toSet() return suggestedPaths .asSequence() .filterNot { it in existingPaths } .distinct() .map { PyDetectedSdk(it) } - .sortedWith(compareBy({ it.isAssociatedWithProject(project) }, + .sortedWith(compareBy({ it.isAssociatedWithModule(module) }, { it.homePath }).reversed()) .toList() } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkAdditionalData.java b/python/src/com/jetbrains/python/sdk/PythonSdkAdditionalData.java index f4cc414d79a8..003fe78e4e4a 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkAdditionalData.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkAdditionalData.java @@ -17,6 +17,7 @@ package com.jetbrains.python.sdk; import com.google.common.collect.Sets; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkAdditionalData; @@ -53,7 +54,7 @@ public class PythonSdkAdditionalData implements SdkAdditionalData { private final VirtualFilePointerContainer myExcludedPaths; private final PythonSdkFlavor myFlavor; - private String myAssociatedProjectPath; + private String myAssociatedModulePath; private boolean myAssociateWithNewProject; private boolean myIsPipEnv; @@ -87,19 +88,23 @@ public class PythonSdkAdditionalData implements SdkAdditionalData { } } - public String getAssociatedProjectPath() { - return myAssociatedProjectPath; + public String getAssociatedModulePath() { + return myAssociatedModulePath; } - public void setAssociatedProjectPath(@Nullable String associatedProjectPath) { - myAssociatedProjectPath = associatedProjectPath; + public void resetAssociatedModulePath() { + setAssociatedModulePath(null); + } + + public void setAssociatedModulePath(@Nullable String associatedModulePath) { + myAssociatedModulePath = associatedModulePath; myAssociateWithNewProject = false; } - public void associateWithProject(@NotNull Project project) { - final String path = project.getBasePath(); + public void associateWithModule(@NotNull Module module) { + final String path = PySdkExtKt.getBasePath(module); if (path != null) { - myAssociatedProjectPath = FileUtil.toSystemIndependentName(path); + myAssociatedModulePath = FileUtil.toSystemIndependentName(path); } myAssociateWithNewProject = false; } @@ -108,9 +113,9 @@ public class PythonSdkAdditionalData implements SdkAdditionalData { myAssociateWithNewProject = true; } - public void reassociateWithCreatedProject(@NotNull Project project) { + public void reAssociateWithCreatedProject(@NotNull Project project) { if (myAssociateWithNewProject) { - associateWithProject(project); + setAssociatedModulePath(project.getBasePath()); } } @@ -126,8 +131,8 @@ public class PythonSdkAdditionalData implements SdkAdditionalData { savePaths(rootElement, myAddedPaths, PATHS_ADDED_BY_USER_ROOT, PATH_ADDED_BY_USER); savePaths(rootElement, myExcludedPaths, PATHS_REMOVED_BY_USER_ROOT, PATH_REMOVED_BY_USER); - if (myAssociatedProjectPath != null) { - rootElement.setAttribute(ASSOCIATED_PROJECT_PATH, myAssociatedProjectPath); + if (myAssociatedModulePath != null) { + rootElement.setAttribute(ASSOCIATED_PROJECT_PATH, myAssociatedModulePath); // XXX: We have to persist the pipenv flag since pipenv is no different from a regular // virtualenv and currently we want to handle pipenvs differently. Consider adding an SDK // extension mechanism for that @@ -163,7 +168,7 @@ public class PythonSdkAdditionalData implements SdkAdditionalData { collectPaths(JDOMExternalizer.loadStringsList(element, PATHS_ADDED_BY_USER_ROOT, PATH_ADDED_BY_USER),myAddedPaths); collectPaths(JDOMExternalizer.loadStringsList(element, PATHS_REMOVED_BY_USER_ROOT, PATH_REMOVED_BY_USER),myExcludedPaths); if (element != null) { - data.setAssociatedProjectPath(element.getAttributeValue(ASSOCIATED_PROJECT_PATH)); + data.setAssociatedModulePath(element.getAttributeValue(ASSOCIATED_PROJECT_PATH)); data.setPipEnv("true".equals(element.getAttributeValue(IS_PIPENV))); } } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java b/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java index 7cb0c8de1789..0383499b49da 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkDetailsStep.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.sdk; +import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.ui.DialogWrapper; @@ -38,7 +39,8 @@ import java.util.List; public class PythonSdkDetailsStep extends BaseListPopupStep { @Nullable private final DialogWrapper myShowAll; - private final Project myProject; + @Nullable private final Project myProject; + @Nullable private final Module myModule; private final Sdk[] myExistingSdks; private final NullableConsumer mySdkAddedCallback; @@ -47,13 +49,14 @@ public class PythonSdkDetailsStep extends BaseListPopupStep { @Nullable private String myNewProjectPath; public static void show(@Nullable final Project project, + @Nullable final Module module, @NotNull final Sdk[] existingSdks, @Nullable final DialogWrapper showAllDialog, @NotNull JComponent ownerComponent, @NotNull final Point popupPoint, @Nullable String newProjectPath, @NotNull final NullableConsumer sdkAddedCallback) { - final PythonSdkDetailsStep sdkHomesStep = new PythonSdkDetailsStep(project, showAllDialog, existingSdks, sdkAddedCallback); + final PythonSdkDetailsStep sdkHomesStep = new PythonSdkDetailsStep(project, module, showAllDialog, existingSdks, sdkAddedCallback); if (showAllDialog == null) { sdkHomesStep.createLocalSdk(); } @@ -65,11 +68,13 @@ public class PythonSdkDetailsStep extends BaseListPopupStep { } public PythonSdkDetailsStep(@Nullable final Project project, + @Nullable final Module module, @Nullable final DialogWrapper showAllDialog, @NotNull final Sdk[] existingSdks, @NotNull final NullableConsumer sdkAddedCallback) { super(null, getAvailableOptions(showAllDialog != null)); myProject = project; + myModule = module; myShowAll = showAllDialog; myExistingSdks = existingSdks; mySdkAddedCallback = sdkAddedCallback; @@ -103,7 +108,7 @@ public class PythonSdkDetailsStep extends BaseListPopupStep { private void createLocalSdk() { final Project project = myNewProjectPath != null ? null : myProject; - final PyAddSdkDialog dialog = PyAddSdkDialog.create(project, Arrays.asList(myExistingSdks), myNewProjectPath); + final PyAddSdkDialog dialog = PyAddSdkDialog.create(project, myModule, Arrays.asList(myExistingSdks), myNewProjectPath); final Sdk sdk = dialog.showAndGet() ? dialog.getOrCreateSdk() : null; mySdkAddedCallback.consume(sdk); } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java index 1afbf8543244..590547dde973 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java @@ -289,7 +289,7 @@ public final class PythonSdkType extends SdkType { if (pointerInfo == null) return; final Point point = pointerInfo.getLocation(); PythonSdkDetailsStep - .show(project, sdkModel.getSdks(), null, parentComponent, point, null, sdk -> { + .show(project, null, sdkModel.getSdks(), null, parentComponent, point, null, sdk -> { if (sdk != null) { sdk.putUserData(SDK_CREATOR_COMPONENT_KEY, new WeakReference<>(parentComponent)); sdkCreatedCallback.consume(sdk); diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddExistingCondaEnvPanel.kt b/python/src/com/jetbrains/python/sdk/add/PyAddExistingCondaEnvPanel.kt index 95e4aa62bbf3..2208a4c77e46 100644 --- a/python/src/com/jetbrains/python/sdk/add/PyAddExistingCondaEnvPanel.kt +++ b/python/src/com/jetbrains/python/sdk/add/PyAddExistingCondaEnvPanel.kt @@ -15,6 +15,7 @@ */ package com.jetbrains.python.sdk.add +import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.ui.ValidationInfo @@ -29,12 +30,13 @@ import javax.swing.Icon * @author vlan */ class PyAddExistingCondaEnvPanel(private val project: Project?, + private val module: Module?, private val existingSdks: List, override var newProjectPath: String?) : PyAddSdkPanel() { override val panelName: String = "Existing environment" override val icon: Icon = PythonIcons.Python.Condaenv - private val sdkComboBox = PySdkPathChoosingComboBox(detectCondaEnvs(project, existingSdks) - .filterNot { it.isAssociatedWithAnotherProject(project) }, + private val sdkComboBox = PySdkPathChoosingComboBox(detectCondaEnvs(module, existingSdks) + .filterNot { it.isAssociatedWithAnotherModule(module) }, null) private val makeSharedField = JBCheckBox("Make available to all projects") @@ -54,7 +56,7 @@ class PyAddExistingCondaEnvPanel(private val project: Project?, return when (sdk) { is PyDetectedSdk -> sdk.setupAssociated(existingSdks, newProjectPath ?: project?.basePath)?.apply { if (!makeSharedField.isSelected) { - associateWithProject(project, newProjectPath != null) + associateWithModule(module, newProjectPath != null) } } else -> sdk diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddExistingVirtualEnvPanel.kt b/python/src/com/jetbrains/python/sdk/add/PyAddExistingVirtualEnvPanel.kt index 4399abc20877..57a1f17d0006 100644 --- a/python/src/com/jetbrains/python/sdk/add/PyAddExistingVirtualEnvPanel.kt +++ b/python/src/com/jetbrains/python/sdk/add/PyAddExistingVirtualEnvPanel.kt @@ -15,6 +15,7 @@ */ package com.jetbrains.python.sdk.add +import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.ui.ValidationInfo @@ -29,12 +30,13 @@ import javax.swing.Icon * @author vlan */ class PyAddExistingVirtualEnvPanel(private val project: Project?, + private val module: Module?, private val existingSdks: List, override var newProjectPath: String?) : PyAddSdkPanel() { override val panelName: String = "Existing environment" override val icon: Icon = PythonIcons.Python.Virtualenv - private val sdkComboBox = PySdkPathChoosingComboBox(detectVirtualEnvs(project, existingSdks) - .filterNot { it.isAssociatedWithAnotherProject(project) }, + private val sdkComboBox = PySdkPathChoosingComboBox(detectVirtualEnvs(module, existingSdks) + .filterNot { it.isAssociatedWithAnotherModule(module) }, null) private val makeSharedField = JBCheckBox("Make available to all projects") @@ -56,7 +58,7 @@ class PyAddExistingVirtualEnvPanel(private val project: Project?, return when (sdk) { is PyDetectedSdk -> sdk.setupAssociated(existingSdks, newProjectPath ?: project?.basePath)?.apply { if (!makeSharedField.isSelected) { - associateWithProject(project, newProjectPath != null) + associateWithModule(module, newProjectPath != null) } } else -> sdk diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddNewCondaEnvPanel.kt b/python/src/com/jetbrains/python/sdk/add/PyAddNewCondaEnvPanel.kt index b26129d702f8..3db776bbd037 100644 --- a/python/src/com/jetbrains/python/sdk/add/PyAddNewCondaEnvPanel.kt +++ b/python/src/com/jetbrains/python/sdk/add/PyAddNewCondaEnvPanel.kt @@ -17,6 +17,7 @@ package com.jetbrains.python.sdk.add import com.intellij.execution.ExecutionException import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory +import com.intellij.openapi.module.Module import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project @@ -34,7 +35,8 @@ import com.intellij.util.ui.FormBuilder import com.jetbrains.python.packaging.PyCondaPackageManagerImpl import com.jetbrains.python.packaging.PyCondaPackageService import com.jetbrains.python.psi.LanguageLevel -import com.jetbrains.python.sdk.associateWithProject +import com.jetbrains.python.sdk.associateWithModule +import com.jetbrains.python.sdk.basePath import com.jetbrains.python.sdk.createSdkByGenerateTask import icons.PythonIcons import org.jetbrains.annotations.SystemIndependent @@ -49,6 +51,7 @@ import javax.swing.event.DocumentEvent * @author vlan */ class PyAddNewCondaEnvPanel(private val project: Project?, + private val module: Module?, private val existingSdks: List, newProjectPath: String?) : PyAddNewEnvPanel() { override val envName: String = "Conda" @@ -117,10 +120,10 @@ class PyAddNewCondaEnvPanel(private val project: Project?, } } val shared = makeSharedField.isSelected - val associatedPath = if (!shared) newProjectPath ?: project?.basePath else null + val associatedPath = if (!shared) projectBasePath else null val sdk = createSdkByGenerateTask(task, existingSdks, null, associatedPath, null) ?: return null if (!shared) { - sdk.associateWithProject(project, newProjectPath != null) + sdk.associateWithModule(module, newProjectPath != null) } PyCondaPackageService.getInstance().PREFERRED_CONDA_PATH = condaPath return sdk @@ -162,7 +165,7 @@ class PyAddNewCondaEnvPanel(private val project: Project?, } private val projectBasePath: @SystemIndependent String? - get() = newProjectPath ?: project?.basePath + get() = newProjectPath ?: module?.basePath ?: project?.basePath private val selectedLanguageLevel: String get() = languageLevelsField.getItemAt(languageLevelsField.selectedIndex) diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddNewVirtualEnvPanel.kt b/python/src/com/jetbrains/python/sdk/add/PyAddNewVirtualEnvPanel.kt index ddd8fad4a566..de9ff50eba14 100644 --- a/python/src/com/jetbrains/python/sdk/add/PyAddNewVirtualEnvPanel.kt +++ b/python/src/com/jetbrains/python/sdk/add/PyAddNewVirtualEnvPanel.kt @@ -18,6 +18,7 @@ package com.jetbrains.python.sdk.add import com.intellij.execution.ExecutionException import com.intellij.openapi.application.WriteAction import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory +import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.Task @@ -46,6 +47,7 @@ import javax.swing.event.DocumentEvent * @author vlan */ class PyAddNewVirtualEnvPanel(private val project: Project?, + private val module: Module?, private val existingSdks: List, newProjectPath: String?) : PyAddNewEnvPanel() { override val envName: String = "Virtualenv" @@ -106,10 +108,10 @@ class PyAddNewVirtualEnvPanel(private val project: Project?, } } val shared = makeSharedField.isSelected - val associatedPath = if (!shared) newProjectPath ?: project?.basePath else null + val associatedPath = if (!shared) projectBasePath else null val sdk = createSdkByGenerateTask(task, existingSdks, baseSdkField.selectedSdk, associatedPath, null) ?: return null if (!shared) { - sdk.associateWithProject(project, newProjectPath != null) + sdk.associateWithModule(module, newProjectPath != null) } excludeDirectoryFromProject(root, project) with(PySdkSettings.instance) { @@ -148,7 +150,7 @@ class PyAddNewVirtualEnvPanel(private val project: Project?, } private val projectBasePath: @SystemIndependent String - get() = newProjectPath ?: project?.basePath ?: userHome + get() = newProjectPath ?: module?.basePath ?: project?.basePath ?: userHome private val userHome: @SystemIndependent String get() = FileUtil.toSystemIndependentName(SystemProperties.getUserHome()) diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddPipEnvPanel.kt b/python/src/com/jetbrains/python/sdk/add/PyAddPipEnvPanel.kt index 99a7e97a78f9..e5033c911dca 100644 --- a/python/src/com/jetbrains/python/sdk/add/PyAddPipEnvPanel.kt +++ b/python/src/com/jetbrains/python/sdk/add/PyAddPipEnvPanel.kt @@ -1,45 +1,63 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python.sdk.add +import com.intellij.application.options.ModuleListCellRenderer +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk -import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.ValidationInfo -import com.intellij.openapi.vfs.StandardFileSystems -import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.components.JBCheckBox +import com.intellij.util.PlatformUtils import com.intellij.util.ui.FormBuilder +import com.jetbrains.python.PythonModuleTypeBase import com.jetbrains.python.psi.LanguageLevel -import com.jetbrains.python.sdk.associatedProjectPath +import com.jetbrains.python.sdk.associatedModulePath +import com.jetbrains.python.sdk.basePath import com.jetbrains.python.sdk.flavors.* import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.event.ItemEvent import javax.swing.Icon import javax.swing.JComboBox /** * The UI panel for adding the pipenv interpreter for the project. * - * TODO: Pass a module here, since pipenv interpreters are per module, not per project. - * * @author vlan */ class PyAddPipEnvPanel(private val project: Project?, + private val module: Module?, private val existingSdks: List, override var newProjectPath: String?) : PyAddNewEnvPanel() { override val envName = "Pipenv" override val panelName = "Pipenv Environment" override val icon: Icon = PIPENV_ICON - private val DEFAULT_PYTHON = "Default" + private val DEFAULT_PYTHON = "" + private val moduleField: JComboBox private val languageLevelsField: JComboBox private val installPackagesCheckBox = JBCheckBox("Install packages from Pipfile").apply { - isEnabled = projectPipFile != null - isVisible = newProjectPath != null + isVisible = newProjectPath == null } init { layout = BorderLayout() + val modules = project?.let { + ModuleUtil.getModulesOfType(it, PythonModuleTypeBase.getInstance()) + } ?: emptyList() + + moduleField = JComboBox(modules.toTypedArray()).apply { + renderer = ModuleListCellRenderer() + preferredSize = Dimension(Int.MAX_VALUE, preferredSize.height) + addItemListener { + if (it.stateChange == ItemEvent.SELECTED) { + updateInstallPackagesCheckBox() + } + } + } + val supportedLanguageLevels = listOf(DEFAULT_PYTHON, LanguageLevel.PYTHON37, @@ -49,26 +67,39 @@ class PyAddPipEnvPanel(private val project: Project?, LanguageLevel.PYTHON27) .map { it.toString() } - languageLevelsField = ComboBox(supportedLanguageLevels.toTypedArray()).apply { + languageLevelsField = JComboBox(supportedLanguageLevels.toTypedArray()).apply { selectedItem = if (itemCount > 0) getItemAt(0) else null } - val formPanel = FormBuilder.createFormBuilder() - .addLabeledComponent("Python version:", languageLevelsField) - .addComponent(installPackagesCheckBox) - .panel - add(formPanel, BorderLayout.NORTH) + val builder = FormBuilder.createFormBuilder().apply { + if (module == null && modules.size > 1) { + val associatedObject = if (PlatformUtils.isPyCharm()) "project" else "module" + addLabeledComponent("Associated $associatedObject:", moduleField) + } + addLabeledComponent("Python version:", languageLevelsField) + addComponent(installPackagesCheckBox) + } + add(builder.panel, BorderLayout.NORTH) + updateInstallPackagesCheckBox() } override fun getOrCreateSdk(): Sdk? { - // TODO: Pass a module here for selecting the proper Pipfile - return setupPipEnvSdkUnderProgress(project, null, existingSdks, newProjectPath, + return setupPipEnvSdkUnderProgress(project, selectedModule, existingSdks, newProjectPath, selectedLanguageLevel, installPackagesCheckBox.isSelected) } override fun validateAll(): List = listOfNotNull(validatePipEnvExecutable(), validatePipEnvIsNotAdded()) + private fun updateInstallPackagesCheckBox() { + selectedModule?.let { + installPackagesCheckBox.isEnabled = it.pipFiles.isNotEmpty() + } + } + + private val selectedModule: Module? + get() = module ?: moduleField.selectedItem as? Module + /** * Checks if `pipenv` is available on `$PATH`. */ @@ -84,7 +115,7 @@ class PyAddPipEnvPanel(private val project: Project?, private fun validatePipEnvIsNotAdded(): ValidationInfo? { val path = projectPath ?: return null val addedPipEnv = existingSdks.find { - it.associatedProjectPath == path && it.isPipEnv + it.associatedModulePath == path && it.isPipEnv } ?: return null return ValidationInfo("""Pipenv interpreter has been already added, select "${addedPipEnv.name}" in your interpreters list""") } @@ -93,7 +124,7 @@ class PyAddPipEnvPanel(private val project: Project?, * The effective project path for the new project or for the existing project. */ private val projectPath: String? - get() = newProjectPath ?: project?.basePath + get() = newProjectPath ?: selectedModule?.basePath ?: project?.basePath /** * The version of Python selected by the user or `null` if the default version is selected. @@ -103,13 +134,4 @@ class PyAddPipEnvPanel(private val project: Project?, val text = languageLevelsField.getItemAt(languageLevelsField.selectedIndex) return if (text == DEFAULT_PYTHON) null else text } - - /** - * The Pipfile for the effective project path. - */ - private val projectPipFile: VirtualFile? - get() { - val path = projectPath ?: return null - return StandardFileSystems.local().findFileByPath(path)?.findChild(PIP_FILE) - } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddSdkDialog.kt b/python/src/com/jetbrains/python/sdk/add/PyAddSdkDialog.kt index 0afcaca2e660..5058d63363af 100644 --- a/python/src/com/jetbrains/python/sdk/add/PyAddSdkDialog.kt +++ b/python/src/com/jetbrains/python/sdk/add/PyAddSdkDialog.kt @@ -16,6 +16,7 @@ package com.jetbrains.python.sdk.add import com.intellij.openapi.Disposable +import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.ui.DialogWrapper @@ -37,7 +38,7 @@ import com.jetbrains.python.sdk.PythonSdkType import com.jetbrains.python.sdk.add.PyAddSdkDialog.Companion.create import com.jetbrains.python.sdk.add.PyAddSdkDialogFlowAction.* import com.jetbrains.python.sdk.detectVirtualEnvs -import com.jetbrains.python.sdk.isAssociatedWithProject +import com.jetbrains.python.sdk.isAssociatedWithModule import icons.PythonIcons import java.awt.CardLayout import java.awt.event.ActionEvent @@ -54,6 +55,7 @@ import javax.swing.JPanel * @author vlan */ class PyAddSdkDialog private constructor(private val project: Project?, + private val module: Module?, private val existingSdks: List, private val newProjectPath: String?) : DialogWrapper(project) { /** @@ -72,12 +74,15 @@ class PyAddSdkDialog private constructor(private val project: Project?, val sdks = existingSdks .filter { it.sdkType is PythonSdkType && !PythonSdkType.isInvalid(it) } .sortedWith(PreferredSdkComparator()) - val panels = arrayListOf(createVirtualEnvPanel(project, sdks, newProjectPath), + val panels = arrayListOf(createVirtualEnvPanel(project, module, sdks, newProjectPath), createPipEnvPanel(), - createAnacondaPanel(project), + createAnacondaPanel(project, module), PyAddSystemWideInterpreterPanel(existingSdks)) val extendedPanels = PyAddSdkProvider.EP_NAME.extensions - .mapNotNull { it.createView(project = project, newProjectPath = newProjectPath, existingSdks = existingSdks).registerIfDisposable() } + .mapNotNull { + it.createView(project = project, module = module, newProjectPath = newProjectPath, existingSdks = existingSdks) + .registerIfDisposable() + } panels.addAll(extendedPanels) mainPanel.add(SPLITTER_COMPONENT_CARD_PANE, createCardSplitter(panels)) return mainPanel @@ -202,37 +207,38 @@ class PyAddSdkDialog private constructor(private val project: Project?, } private fun createVirtualEnvPanel(project: Project?, + module: Module?, existingSdks: List, newProjectPath: String?): PyAddSdkPanel { val newVirtualEnvPanel = when { - allowCreatingNewEnvironments(project) -> PyAddNewVirtualEnvPanel(project, existingSdks, newProjectPath) + allowCreatingNewEnvironments(project) -> PyAddNewVirtualEnvPanel(project, module, existingSdks, newProjectPath) else -> null } - val existingVirtualEnvPanel = PyAddExistingVirtualEnvPanel(project, existingSdks, newProjectPath) + val existingVirtualEnvPanel = PyAddExistingVirtualEnvPanel(project, module, existingSdks, newProjectPath) val panels = listOf(newVirtualEnvPanel, existingVirtualEnvPanel) .filterNotNull() val defaultPanel = when { - detectVirtualEnvs(project, existingSdks).any { it.isAssociatedWithProject(project) } -> existingVirtualEnvPanel + detectVirtualEnvs(module, existingSdks).any { it.isAssociatedWithModule(module) } -> existingVirtualEnvPanel newVirtualEnvPanel != null -> newVirtualEnvPanel else -> existingVirtualEnvPanel } return PyAddSdkGroupPanel("Virtualenv environment", PythonIcons.Python.Virtualenv, panels, defaultPanel) } - private fun createAnacondaPanel(project: Project?): PyAddSdkPanel { + private fun createAnacondaPanel(project: Project?, module: Module?): PyAddSdkPanel { val newCondaEnvPanel = when { - allowCreatingNewEnvironments(project) -> PyAddNewCondaEnvPanel(project, existingSdks, newProjectPath) + allowCreatingNewEnvironments(project) -> PyAddNewCondaEnvPanel(project, module, existingSdks, newProjectPath) else -> null } val panels = listOf(newCondaEnvPanel, - PyAddExistingCondaEnvPanel(project, existingSdks, newProjectPath)) + PyAddExistingCondaEnvPanel(project, module, existingSdks, newProjectPath)) .filterNotNull() return PyAddSdkGroupPanel("Conda environment", PythonIcons.Python.Anaconda, panels, panels[0]) } private fun createPipEnvPanel(): PyAddSdkPanel { - return PyAddPipEnvPanel(project, existingSdks, newProjectPath) + return PyAddPipEnvPanel(project, module, existingSdks, newProjectPath) } /** @@ -327,8 +333,10 @@ class PyAddSdkDialog private constructor(private val project: Project?, private const val WIZARD_CARD_PANE = "Wizard" @JvmStatic - fun create(project: Project?, existingSdks: List, newProjectPath: String?): PyAddSdkDialog { - return PyAddSdkDialog(project = project, existingSdks = existingSdks, newProjectPath = newProjectPath).apply { init() } + fun create(project: Project?, module: Module?, existingSdks: List, newProjectPath: String?): PyAddSdkDialog { + return PyAddSdkDialog(project = project, module = module, existingSdks = existingSdks, newProjectPath = newProjectPath).apply { + init() + } } } } diff --git a/python/src/com/jetbrains/python/sdk/add/PyAddSdkProvider.kt b/python/src/com/jetbrains/python/sdk/add/PyAddSdkProvider.kt index 1c4758f862bc..18b346a17f38 100644 --- a/python/src/com/jetbrains/python/sdk/add/PyAddSdkProvider.kt +++ b/python/src/com/jetbrains/python/sdk/add/PyAddSdkProvider.kt @@ -2,6 +2,7 @@ package com.jetbrains.python.sdk.add import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk @@ -9,7 +10,7 @@ interface PyAddSdkProvider { /** * Returns [PyAddSdkView] if applicable. */ - fun createView(project: Project?, newProjectPath: String?, existingSdks: List): PyAddSdkView? + fun createView(project: Project?, module: Module?, newProjectPath: String?, existingSdks: List): PyAddSdkView? companion object { val EP_NAME: ExtensionPointName = ExtensionPointName.create("Pythonid.pyAddSdkProvider") diff --git a/python/src/com/jetbrains/python/sdk/flavors/VirtualEnvSdkFlavor.java b/python/src/com/jetbrains/python/sdk/flavors/VirtualEnvSdkFlavor.java index 3034a11072b1..217cd3eb83fd 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/VirtualEnvSdkFlavor.java +++ b/python/src/com/jetbrains/python/sdk/flavors/VirtualEnvSdkFlavor.java @@ -17,6 +17,8 @@ package com.jetbrains.python.sdk.flavors; import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; @@ -24,6 +26,8 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.SystemProperties; +import com.jetbrains.python.PythonModuleTypeBase; +import com.jetbrains.python.sdk.PySdkExtKt; import com.jetbrains.python.sdk.PythonSdkType; import icons.PythonIcons; import org.jetbrains.annotations.NotNull; @@ -50,9 +54,12 @@ public class VirtualEnvSdkFlavor extends CPythonSdkFlavor { final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext()); List candidates = new ArrayList<>(); if (project != null) { - VirtualFile rootDir = project.getBaseDir(); - if (rootDir != null) - candidates.addAll(findInDirectory(rootDir)); + for (Module module : ModuleUtil.getModulesOfType(project, PythonModuleTypeBase.getInstance())) { + final VirtualFile baseDir = PySdkExtKt.getBaseDir(module); + if (baseDir != null) { + candidates.addAll(findInDirectory(baseDir)); + } + } } final VirtualFile path = getDefaultLocation(); diff --git a/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt b/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt index 5f289db138ce..18b6c2a91b41 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt +++ b/python/src/com/jetbrains/python/sdk/flavors/pipenv.kt @@ -27,7 +27,6 @@ import com.jetbrains.python.packaging.PyExecutionException import com.jetbrains.python.sdk.* import icons.PythonIcons import org.jetbrains.annotations.SystemDependent -import org.jetbrains.jps.model.serialization.PathMacroUtil import java.io.File import javax.swing.Icon @@ -94,7 +93,7 @@ fun setupPipEnvSdkUnderProgress(project: Project?, val suggestedName = "Pipenv (${PathUtil.getFileName(projectPath)})" return createSdkByGenerateTask(task, existingSdks, null, projectPath, suggestedName)?.apply { isPipEnv = true - associateWithProject(project, newProjectPath != null) + associateWithModule(module, newProjectPath != null) } } @@ -193,9 +192,3 @@ class UsePipEnvQuickFix : LocalQuickFix { } } } - -/** - * Returns the directory with the module file taking into account .idea/. - */ -private val Module.basePath: String? - get() = PathMacroUtil.getModuleDir(moduleFilePath)