second iteration for python project interpreters page

Added More/Show All buttons
filter out symlinks from suggested interpreters homepath
Add detected sdk to project
Sorting for interpreters combobox
Added separators to interpreter combobox
This commit is contained in:
Ekaterina Tuzova
2014-02-11 12:50:34 +04:00
parent 6820fe5453
commit 846cd8372d
11 changed files with 177 additions and 34 deletions
@@ -224,7 +224,7 @@ public class PyIdeCommonOptionsForm implements AbstractPyCommonOptionsForm {
}
public void updateSdkList(boolean preserveSelection, PyConfigurableInterpreterList myInterpreterList) {
myPythonSdks = myInterpreterList.getAllPythonSdks();
myPythonSdks = myInterpreterList.getAllPythonSdks(myProject);
Sdk selection = preserveSelection ? (Sdk)myInterpreterComboBox.getSelectedItem() : null;
if (!myPythonSdks.contains(selection)) {
selection = null;
@@ -16,6 +16,7 @@
package com.jetbrains.python.configuration;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.ex.EditorEx;
@@ -27,12 +28,14 @@ import com.intellij.openapi.options.UnnamedConfigurable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkModel;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.projectRoots.impl.SdkListCellRenderer;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.CollectionComboBoxModel;
import com.intellij.ui.awt.RelativePoint;
@@ -42,8 +45,7 @@ import com.intellij.webcore.packaging.PackagesNotificationPanel;
import com.jetbrains.python.packaging.ui.PyInstalledPackagesPanel;
import com.jetbrains.python.packaging.ui.PyPackageManagementService;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.sdk.DetailsChooser;
import com.jetbrains.python.sdk.PySdkListCellRenderer;
import com.jetbrains.python.sdk.*;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
import icons.PythonIcons;
import org.jetbrains.annotations.NotNull;
@@ -53,6 +55,7 @@ import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
public class PyActiveSdkConfigurable implements UnnamedConfigurable {
@@ -66,6 +69,7 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
private ComboBox mySdkCombo;
private PyInstalledPackagesPanel myPackagesPanel;
private JButton myDetailsButton;
private static final String SHOW_ALL = "Show All";
public PyActiveSdkConfigurable(@NotNull Project project) {
myModule = null;
@@ -101,7 +105,7 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
myDetailsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
DetailsChooser.show(myProject, myProjectSdksModel.getSdks(),
DetailsChooser.show(myProject, myProjectSdksModel.getSdks(), new PythonSdkConfigurable(myProject).createComponent(),
RelativePoint.fromScreen(myDetailsButton.getLocationOnScreen()), true,
new NullableConsumer<Sdk>() {
@Override
@@ -114,8 +118,8 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
myInterpreterList.setSelectedSdk(sdk);
mySdkCombo.getModel().setSelectedItem(sdk);
myPackagesPanel
.updatePackages(new PyPackageManagementService(myProject, sdk));
myPackagesPanel.updatePackages(
new PyPackageManagementService(myProject, sdk));
}
}
);
@@ -130,7 +134,22 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
myPanel = new JPanel(layout);
final JLabel label = new JLabel("Project Interpreter:");
final JLabel label1 = new JLabel(" ");
mySdkCombo = new ComboBox();
mySdkCombo = new ComboBox() {
@Override
public void setSelectedItem(Object item)
{
if (SHOW_ALL.equals(item)) {
DialogBuilder dialog = new DialogBuilder(myProject);
dialog.setTitle("Python Interpreters");
dialog.setCenterPanel(new PythonSdkConfigurable(myProject).createComponent());
dialog.show();
return;
}
if (!PySdkListCellRenderer.SEPARATOR.equals(item.toString()))
super.setSelectedItem(item);
}
};
mySdkCombo.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
mySdkCombo.setRenderer(new SdkListCellRenderer("<None>"));
PackagesNotificationPanel notificationsArea = new PackagesNotificationPanel(myProject);
@@ -199,7 +218,8 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
@Override
public boolean isModified() {
final Sdk sdk = getSdk();
return sdk != myProjectSdksModel.findSdk((Sdk)mySdkCombo.getSelectedItem());
final Sdk selectedItem = (Sdk)mySdkCombo.getSelectedItem();
return selectedItem instanceof PyDetectedSdk || sdk != myProjectSdksModel.findSdk(selectedItem);
}
@Nullable
@@ -213,8 +233,18 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
@Override
public void apply() throws ConfigurationException {
final Sdk item = (Sdk)mySdkCombo.getSelectedItem();
if (item instanceof PyDetectedSdk) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
final Sdk sdk = SdkConfigurationUtil.createAndAddSDK(item.getName(), PythonSdkType.getInstance());
SdkConfigurationUtil.setDirectoryProjectSdk(myProject, sdk);
}
}, ModalityState.any());
}
myProjectSdksModel.apply();
final Sdk selectedSdk = myProjectSdksModel.findSdk((Sdk)mySdkCombo.getSelectedItem());
final Sdk selectedSdk = myProjectSdksModel.findSdk(item);
if (myModule == null) {
final ProjectRootManager rootManager = ProjectRootManager.getInstance(myProject);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@@ -280,7 +310,7 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
}
private void updateSdkList(boolean preserveSelection) {
final List<Sdk> sdkList = myInterpreterList.getAllPythonSdks();
final List<Sdk> sdkList = myInterpreterList.getAllPythonSdks(myProject);
Sdk selection = preserveSelection ? (Sdk)mySdkCombo.getSelectedItem() : null;
if (!sdkList.contains(selection)) {
selection = null;
@@ -290,9 +320,44 @@ public class PyActiveSdkConfigurable implements UnnamedConfigurable {
if (selection != null && !sdkList.contains(selection)) {
sdkList.add(0, selection);
}
sdkList.add(0, null);
List<Object> items = new ArrayList<Object>();
items.add(null);
boolean remoteSeparator = true;
boolean separator = true;
boolean detectedSeparator = true;
boolean hasAssociationsWithDifferentProject = false;
final String projectBasePath = myProject.getBasePath();
for (Sdk sdk : sdkList) {
if (!PythonSdkType.isVirtualEnv(sdk) && !PythonSdkType.isRemote(sdk) &&
!(sdk instanceof PyDetectedSdk) && separator) {
items.add(PySdkListCellRenderer.SEPARATOR);
separator = false;
}
if (PythonSdkType.isRemote(sdk) && remoteSeparator) {
items.add(PySdkListCellRenderer.SEPARATOR);
remoteSeparator = false;
}
if (sdk instanceof PyDetectedSdk && detectedSeparator) {
items.add(PySdkListCellRenderer.SEPARATOR);
detectedSeparator = false;
}
items.add(sdk);
final PythonSdkAdditionalData data = (PythonSdkAdditionalData)sdk.getSdkAdditionalData();
if (data != null) {
final String path = data.getAssociatedProjectPath();
if (path != null && !path.equals(projectBasePath)) hasAssociationsWithDifferentProject = true;
}
}
if (hasAssociationsWithDifferentProject) {
items.add(PySdkListCellRenderer.SEPARATOR);
items.add(SHOW_ALL);
}
mySdkCombo.setRenderer(new PySdkListCellRenderer());
mySdkCombo.setModel(new CollectionComboBoxModel(sdkList, selection));
mySdkCombo.setModel(new CollectionComboBoxModel(items, selection));
}
@Override
@@ -18,15 +18,18 @@ package com.jetbrains.python.configuration;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
import com.intellij.openapi.util.Comparing;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.sdk.PyDetectedSdk;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkAdditionalData;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.*;
/**
* Manages the SDK model shared between PythonSdkConfigurable and PyActiveSdkConfigurable.
@@ -65,19 +68,66 @@ public class PyConfigurableInterpreterList {
}
}
public List<Sdk> getAllPythonSdks() {
public List<Sdk> getAllPythonSdks(final Project project) {
List<Sdk> result = new ArrayList<Sdk>();
for (Sdk sdk : getModel().getSdks()) {
if (sdk.getSdkType() instanceof PythonSdkType) {
result.add(sdk);
}
}
Collection<String> sdkHomes = PythonSdkType.getInstance().suggestHomePaths();
Collections.sort(result, new Comparator<Sdk>() {
@Override
public int compare(Sdk o1, Sdk o2) {
if (!(o1.getSdkType() instanceof PythonSdkType) ||
!(o2.getSdkType() instanceof PythonSdkType))
return -Comparing.compare(o1.getName(), o2.getName());
final boolean isVEnv1 = PythonSdkType.isVirtualEnv(o1);
final boolean isVEnv2 = PythonSdkType.isVirtualEnv(o2);
final boolean isRemote1 = PySdkUtil.isRemote(o1);
final boolean isRemote2 = PySdkUtil.isRemote(o2);
final PythonSdkFlavor flavor1 = PythonSdkFlavor.getFlavor(o1);
final PythonSdkFlavor flavor2 = PythonSdkFlavor.getFlavor(o2);
final LanguageLevel level1 = flavor1 != null ? flavor1.getLanguageLevel(o1) : LanguageLevel.getDefault();
final LanguageLevel level2 = flavor2 != null ? flavor2.getLanguageLevel(o2) : LanguageLevel.getDefault();
if (isVEnv1) {
if (associatedWithCurrent(o1, project)) return -1;
if (isVEnv2) {
final int compare = Comparing.compare(level1, level2);
if (compare != 0) return -compare;
return Comparing.compare(o1.getName(), o2.getName());
}
return -1;
}
if (isVEnv2) {
return 1;
}
if (isRemote1) return 1;
if (isRemote2) return -1;
final int compare = Comparing.compare(level1, level2);
if (compare != 0) return -compare;
return Comparing.compare(o1.getName(), o2.getName());
}
});
for (String sdkHome : SdkConfigurationUtil.filterExistingPaths(PythonSdkType.getInstance(), sdkHomes, getModel().getSdks())) {
result.add(new PyDetectedSdk(sdkHome, PythonSdkType.getInstance()));
}
return result;
}
private static boolean associatedWithCurrent(Sdk o1, Project project) {
final PythonSdkAdditionalData data = (PythonSdkAdditionalData)o1.getSdkAdditionalData();
if (data != null) {
final String path = data.getAssociatedProjectPath();
final String projectBasePath = project.getBasePath();
if (path != null && path.equals(projectBasePath)) {
return true;
}
}
return false;
}
}
@@ -247,7 +247,7 @@ public class PythonSdkConfigurable implements Configurable, Configurable.NoScrol
addCreatedSdk(sdk, true);
}
};
final List<Sdk> allSdks = PyConfigurableInterpreterList.getInstance(myProject).getAllPythonSdks();
final List<Sdk> allSdks = PyConfigurableInterpreterList.getInstance(myProject).getAllPythonSdks(myProject);
final CreateVirtualEnvDialog dialog = new CreateVirtualEnvDialog(myProject, myNewProject, allSdks, sdk);
dialog.show();
if (dialog.isOK()) {
@@ -335,7 +335,7 @@ public class PythonSdkConfigurable implements Configurable, Configurable.NoScrol
}
private void refreshSdkList() {
final List<Sdk> pythonSdks = myInterpreterList.getAllPythonSdks();
final List<Sdk> pythonSdks = myInterpreterList.getAllPythonSdks(myProject);
Sdk projectSdk = myProjectSdksModel.getProjectSdk();
if (!myShowOtherProjectVirtualenvs) {
VirtualEnvProjectFilter.removeNotMatching(myProject, pythonSdks);
@@ -366,7 +366,7 @@ public class PythonSdkConfigurable implements Configurable, Configurable.NoScrol
private void addSdk(AnActionButton button) {
DetailsChooser
.show(myProject, myProjectSdksModel.getSdks(), button.getPreferredPopupPoint(), false, new NullableConsumer<Sdk>() {
.show(myProject, myProjectSdksModel.getSdks(), new PythonSdkConfigurable(myProject).createComponent(), button.getPreferredPopupPoint(), false, new NullableConsumer<Sdk>() {
@Override
public void consume(Sdk sdk) {
myMakeActiveAdded = false;
@@ -22,6 +22,7 @@ import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.*;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
@@ -30,6 +31,7 @@ import com.intellij.util.NullableConsumer;
import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
@@ -39,6 +41,7 @@ import java.util.List;
* @author yole
*/
public class DetailsChooser extends BaseListPopupStep<String> {
private static JComponent myMore;
private final Project myProject;
private final Component myOwnerComponent;
private final Sdk[] myExistingSdks;
@@ -51,9 +54,10 @@ public class DetailsChooser extends BaseListPopupStep<String> {
public static void show(final Project project,
final Sdk[] existingSdks,
final RelativePoint popupPoint,
JComponent component, final RelativePoint popupPoint,
final boolean showMore,
final NullableConsumer<Sdk> callback) {
myMore = component;
final ListPopupStep sdkHomesStep = new DetailsChooser(project, popupPoint.getComponent(), existingSdks, showMore, callback);
final ListPopup popup = JBPopupFactory.getInstance().createListPopup(sdkHomesStep);
popup.show(popupPoint);
@@ -102,7 +106,10 @@ public class DetailsChooser extends BaseListPopupStep<String> {
createVirtualEnvSdk();
}
else {
//createSdkFromPath(selectedValue);
DialogBuilder dialog = new DialogBuilder(myProject);
dialog.setTitle("Python Interpreters");
dialog.setCenterPanel(myMore);
dialog.show();
}
}
@@ -0,0 +1,11 @@
package com.jetbrains.python.sdk;
import com.intellij.openapi.projectRoots.SdkTypeId;
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
public class PyDetectedSdk extends ProjectJdkImpl {
public PyDetectedSdk(String name, SdkTypeId sdkType) {
super(name, sdkType);
}
}
@@ -19,6 +19,7 @@ import com.intellij.icons.AllIcons;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkModificator;
import com.intellij.openapi.projectRoots.SdkType;
import com.intellij.openapi.util.IconLoader;
import com.intellij.ui.LayeredIcon;
import com.intellij.ui.ListCellRendererWrapper;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
@@ -27,9 +28,10 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Map;
public class PySdkListCellRenderer extends ListCellRendererWrapper<Sdk> {
public class PySdkListCellRenderer extends ListCellRendererWrapper<Object> {
private final String myNullText;
private final Map<Sdk, SdkModificator> mySdkModifiers;
public static final String SEPARATOR = "separator";
public PySdkListCellRenderer() {
myNullText = "";
@@ -42,8 +44,9 @@ public class PySdkListCellRenderer extends ListCellRendererWrapper<Sdk> {
}
@Override
public void customize(JList list, Sdk sdk, int index, boolean selected, boolean hasFocus) {
if (sdk != null) {
public void customize(JList list, Object item, int index, boolean selected, boolean hasFocus) {
if (item instanceof Sdk) {
Sdk sdk = (Sdk)item;
final PythonSdkFlavor flavor = PythonSdkFlavor.getPlatformIndependentFlavor(sdk.getHomePath());
final Icon icon = flavor != null ? flavor.getIcon() : ((SdkType)sdk.getSdkType()).getIcon();
@@ -54,7 +57,6 @@ public class PySdkListCellRenderer extends ListCellRendererWrapper<Sdk> {
else {
name = sdk.getName();
}
if (PythonSdkType.isInvalid(sdk)) {
setText("[invalid] " + name);
setIcon(wrapIconWithWarningDecorator(icon));
@@ -63,17 +65,22 @@ public class PySdkListCellRenderer extends ListCellRendererWrapper<Sdk> {
setText("[incomplete] " + name);
setIcon(wrapIconWithWarningDecorator(icon));
}
else if (sdk instanceof PyDetectedSdk){
setText(name);
setIcon(IconLoader.getTransparentIcon(icon));
}
else {
setText(name);
setIcon(icon);
}
}
else {
else if (SEPARATOR.equals(item))
setSeparator();
else if (item == null)
setText(myNullText);
}
}
private LayeredIcon wrapIconWithWarningDecorator(Icon icon) {
private static LayeredIcon wrapIconWithWarningDecorator(Icon icon) {
final LayeredIcon layered = new LayeredIcon(2);
layered.setIcon(icon, 0);
// TODO: Create a separate invalid SDK overlay icon (DSGN-497)
@@ -277,7 +277,7 @@ public class PythonSdkType extends SdkType {
public void showCustomCreateUI(SdkModel sdkModel, final JComponent parentComponent, final Consumer<Sdk> sdkCreatedCallback) {
Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent));
DetailsChooser.show(project, sdkModel.getSdks(), RelativePoint.getCenterOf(parentComponent), true, new NullableConsumer<Sdk>() {
DetailsChooser.show(project, sdkModel.getSdks(), null, RelativePoint.getCenterOf(parentComponent), true, new NullableConsumer<Sdk>() {
@Override
public void consume(@Nullable Sdk sdk) {
if (sdk != null) {
@@ -16,6 +16,7 @@
package com.jetbrains.python.sdk.flavors;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VFileProperty;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
@@ -57,7 +58,7 @@ public class MacPythonSdkFlavor extends CPythonSdkFlavor {
if (binDir != null && binDir.isDirectory()) {
for (String name : POSSIBLE_BINARY_NAMES) {
final VirtualFile child = binDir.findChild(name);
if (child != null) {
if (child != null && !child.is(VFileProperty.SYMLINK)) {
candidates.add(child.getPath());
break;
}
@@ -16,6 +16,7 @@
package com.jetbrains.python.sdk.flavors;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VFileProperty;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
@@ -47,14 +48,15 @@ public class UnixPythonSdkFlavor extends CPythonSdkFlavor {
if (rootDir instanceof NewVirtualFile) {
((NewVirtualFile)rootDir).markDirty();
}
rootDir.refresh(false, false);
rootDir.refresh(true, false);
VirtualFile[] suspects = rootDir.getChildren();
for (VirtualFile child : suspects) {
if (!child.isDirectory()) {
final String childName = child.getName();
for (String name : NAMES) {
if (childName.startsWith(name)) {
if (!childName.endsWith("-config") && !childName.startsWith("pythonw")) {
if (!childName.endsWith("-config") && !childName.startsWith("pythonw") &&
!childName.endsWith("m") && !child.is(VFileProperty.SYMLINK)) {
candidates.add(child.getPath());
}
break;
@@ -83,7 +83,7 @@ public class VirtualEnvSdkFlavor extends CPythonSdkFlavor {
public static Collection<String> findInDirectory(VirtualFile rootDir) {
List<String> candidates = new ArrayList<String>();
if (rootDir != null) {
rootDir.refresh(false, false);
rootDir.refresh(true, false);
VirtualFile[] suspects = rootDir.getChildren();
for (VirtualFile child : suspects) {
if (child.isDirectory()) {