PY-9081 PY-12247 PY-26492 Associate interpreters with modules, not with projects

This commit is contained in:
Andrey Vlasovskikh
2018-06-15 21:24:41 +03:00
parent 31bd9482b4
commit a65a3f894c
23 changed files with 193 additions and 129 deletions
@@ -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;
@@ -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();
@@ -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<Sdk> allPythonSdks = myInterpreterList.getAllPythonSdks(myProject);
final List<Sdk> 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<Sdk> virtualEnvironments = StreamEx
.of(visibleSdks)
@@ -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;
@@ -230,7 +230,7 @@ public class PythonSdkDetailsDialog extends DialogWrapper {
Sdk projectSdk = getSdk();
final List<Sdk> notAssociatedWithOtherProjects = StreamEx
.of(allPythonSdks)
.filter(sdk -> !PySdkExtKt.isAssociatedWithAnotherProject(sdk, myProject))
.filter(sdk -> !PySdkExtKt.isAssociatedWithAnotherModule(sdk, myModule))
.toList();
final List<Sdk> 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();
}
}
@@ -283,7 +283,7 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> 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<T> extends ProjectSettingsStepBase<T> i
final List<Sdk> 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;
}
@@ -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<Sdk>,
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)
}
@@ -27,9 +27,9 @@ class PyAddNewEnvironmentPanel(existingSdks: List<Sdk>, 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]
@@ -75,11 +75,9 @@ public class PythonGenerateProjectCallback<T> 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);
}
}
@@ -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<Sdk> sdks,
@Nullable String newProjectPath,
@NotNull final Condition<Sdk> 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<Sdk> 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) {
+33 -24
View File
@@ -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<Sdk>): List<PyDetectedSdk> {
.toList()
}
fun detectVirtualEnvs(project: Project?, existingSdks: List<Sdk>): List<PyDetectedSdk> =
filterSuggestedPaths(VirtualEnvSdkFlavor.INSTANCE.suggestHomePaths(), existingSdks, project)
fun detectVirtualEnvs(module: Module?, existingSdks: List<Sdk>): List<PyDetectedSdk> =
filterSuggestedPaths(VirtualEnvSdkFlavor.INSTANCE.suggestHomePaths(), existingSdks, module)
fun detectCondaEnvs(project: Project?, existingSdks: List<Sdk>): List<PyDetectedSdk> =
filterSuggestedPaths(CondaEnvSdkFlavor.INSTANCE.suggestHomePaths(), existingSdks, project)
fun detectCondaEnvs(module: Module?, existingSdks: List<Sdk>): List<PyDetectedSdk> =
filterSuggestedPaths(CondaEnvSdkFlavor.INSTANCE.suggestHomePaths(), existingSdks, module)
fun createSdkByGenerateTask(generateSdkHomePath: Task.WithResult<String, ExecutionException>,
existingSdks: List<Sdk>,
@@ -88,30 +90,30 @@ fun createSdkByGenerateTask(generateSdkHomePath: Task.WithResult<String, Executi
false, null, suggestedName) ?: return null
}
fun Sdk.associateWithProject(project: Project?, isNewProject: Boolean) {
fun Sdk.associateWithModule(module: Module?, isNewProject: Boolean) {
getOrCreateAdditionalData().apply {
when {
isNewProject -> 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>): Sdk? {
return SdkConfigurationUtil.setupSdk(existingSdks.toTypedArray(), homeDir, PythonSdkType.getInstance(), false, null, null)
}
fun PyDetectedSdk.setupAssociated(existingSdks: List<Sdk>, associatedProjectPath: String?): Sdk? {
fun PyDetectedSdk.setupAssociated(existingSdks: List<Sdk>, 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<String>,
existingSdks: List<Sdk>,
project: Project?): List<PyDetectedSdk> {
module: Module?): List<PyDetectedSdk> {
val existingPaths = existingSdks.map { it.homePath }.toSet()
return suggestedPaths
.asSequence()
.filterNot { it in existingPaths }
.distinct()
.map { PyDetectedSdk(it) }
.sortedWith(compareBy<PyDetectedSdk>({ it.isAssociatedWithProject(project) },
.sortedWith(compareBy<PyDetectedSdk>({ it.isAssociatedWithModule(module) },
{ it.homePath }).reversed())
.toList()
}
@@ -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)));
}
}
@@ -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<String> {
@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<Sdk> mySdkAddedCallback;
@@ -47,13 +49,14 @@ public class PythonSdkDetailsStep extends BaseListPopupStep<String> {
@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<Sdk> 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<String> {
}
public PythonSdkDetailsStep(@Nullable final Project project,
@Nullable final Module module,
@Nullable final DialogWrapper showAllDialog,
@NotNull final Sdk[] existingSdks,
@NotNull final NullableConsumer<Sdk> 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<String> {
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);
}
@@ -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);
@@ -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<Sdk>,
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
@@ -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<Sdk>,
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
@@ -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<Sdk>,
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)
@@ -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<Sdk>,
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())
@@ -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<Sdk>,
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 = "<Default>"
private val moduleField: JComboBox<Module>
private val languageLevelsField: JComboBox<String>
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<ValidationInfo> =
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)
}
}
@@ -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<Sdk>,
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<PyAddSdkView>(createVirtualEnvPanel(project, sdks, newProjectPath),
val panels = arrayListOf<PyAddSdkView>(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<Sdk>,
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<Sdk>, newProjectPath: String?): PyAddSdkDialog {
return PyAddSdkDialog(project = project, existingSdks = existingSdks, newProjectPath = newProjectPath).apply { init() }
fun create(project: Project?, module: Module?, existingSdks: List<Sdk>, newProjectPath: String?): PyAddSdkDialog {
return PyAddSdkDialog(project = project, module = module, existingSdks = existingSdks, newProjectPath = newProjectPath).apply {
init()
}
}
}
}
@@ -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<Sdk>): PyAddSdkView?
fun createView(project: Project?, module: Module?, newProjectPath: String?, existingSdks: List<Sdk>): PyAddSdkView?
companion object {
val EP_NAME: ExtensionPointName<PyAddSdkProvider> = ExtensionPointName.create("Pythonid.pyAddSdkProvider")
@@ -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<String> 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();
@@ -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)