mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-26372 Refactored new project form validation to take into account new project interpreter fields
This commit is contained in:
+143
-315
@@ -16,90 +16,59 @@
|
||||
package com.jetbrains.python.newProject.steps;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.facet.ui.ValidationResult;
|
||||
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep;
|
||||
import com.intellij.ide.util.projectWizard.ProjectSettingsStepBase;
|
||||
import com.intellij.ide.util.projectWizard.WebProjectTemplate;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.DumbAware;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.ui.*;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.ui.LabeledComponent;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.intellij.openapi.ui.ValidationInfo;
|
||||
import com.intellij.openapi.ui.VerticalFlowLayout;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.platform.DirectoryProjectGenerator;
|
||||
import com.intellij.ui.DocumentAdapter;
|
||||
import com.intellij.ui.HideableDecorator;
|
||||
import com.intellij.ui.TextAccessor;
|
||||
import com.intellij.ui.components.JBRadioButton;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.PathUtil;
|
||||
import com.intellij.util.ui.FormBuilder;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.ui.update.UiNotifyConnector;
|
||||
import com.jetbrains.python.configuration.PyConfigurableInterpreterList;
|
||||
import com.jetbrains.python.configuration.VirtualEnvProjectFilter;
|
||||
import com.jetbrains.python.newProject.PyFrameworkProjectGenerator;
|
||||
import com.jetbrains.python.newProject.PythonProjectGenerator;
|
||||
import com.jetbrains.python.packaging.PyPackage;
|
||||
import com.jetbrains.python.packaging.PyPackageManager;
|
||||
import com.jetbrains.python.packaging.PyPackageUtil;
|
||||
import com.jetbrains.python.remote.PyProjectSynchronizer;
|
||||
import com.jetbrains.python.remote.PythonRemoteInterpreterManager;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import com.jetbrains.python.sdk.PreferredSdkComparator;
|
||||
import com.jetbrains.python.sdk.PyLazySdk;
|
||||
import com.jetbrains.python.sdk.PySdkUtil;
|
||||
import com.jetbrains.python.sdk.PySdkExtKt;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import com.jetbrains.python.sdk.add.PyAddNewVirtualEnvPanel;
|
||||
import icons.PythonIcons;
|
||||
import com.jetbrains.python.sdk.add.PyAddSdkGroupPanel;
|
||||
import com.jetbrains.python.sdk.add.PyAddSdkPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> implements DumbAware {
|
||||
private static final Logger LOGGER = Logger.getInstance(ProjectSpecificSettingsStep.class);
|
||||
private PythonSdkChooserCombo mySdkCombo;
|
||||
private boolean myInstallFramework;
|
||||
private Sdk mySdk;
|
||||
@Nullable private JPanel mySelectedInterpretersPanel;
|
||||
@Nullable private PyAddNewVirtualEnvPanel myNewVirtualEnvPanel;
|
||||
|
||||
/**
|
||||
* For remote projects path for project on remote side
|
||||
*/
|
||||
private PyRemotePathField myRemotePathField;
|
||||
/**
|
||||
* If remote path required for project creation or not
|
||||
*/
|
||||
private boolean myRemotePathRequired;
|
||||
@Nullable private PyAddSdkGroupPanel myInterpreterPanel;
|
||||
|
||||
public ProjectSpecificSettingsStep(@NotNull final DirectoryProjectGenerator<T> projectGenerator,
|
||||
@NotNull final AbstractNewProjectStep.AbstractCallback callback) {
|
||||
super(projectGenerator, callback);
|
||||
}
|
||||
|
||||
private static void acceptsSdk(@NotNull final DirectoryProjectGenerator<?> generator,
|
||||
@NotNull final Sdk sdk,
|
||||
@NotNull final File projectDirectory) throws PythonProjectGenerator.PyNoProjectAllowedOnSdkException {
|
||||
if (generator instanceof PythonProjectGenerator) {
|
||||
((PythonProjectGenerator<?>)generator).checkProjectCanBeCreatedOnSdk(sdk, projectDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JPanel createAndFillContentPanel() {
|
||||
if (myProjectGenerator instanceof PythonProjectGenerator) {
|
||||
@@ -122,10 +91,8 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
|
||||
if (advancedSettings != null) {
|
||||
final JPanel jPanel = new JPanel(new VerticalFlowLayout());
|
||||
final HideableDecorator deco = new HideableDecorator(jPanel, "Mor&e Settings", false);
|
||||
boolean isValid = checkValid();
|
||||
deco.setOn(!isValid);
|
||||
if (myProjectGenerator instanceof PythonProjectGenerator && !deco.isExpanded()) {
|
||||
final ValidationResult result = ((PythonProjectGenerator)myProjectGenerator).warningValidation(getSdk());
|
||||
if (myProjectGenerator instanceof PythonProjectGenerator) {
|
||||
final ValidationResult result = ((PythonProjectGenerator)myProjectGenerator).warningValidation(getInterpreterPanelSdk());
|
||||
deco.setOn(!result.isOk());
|
||||
}
|
||||
deco.setContentComponent(advancedSettings);
|
||||
@@ -137,16 +104,27 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
|
||||
@Nullable
|
||||
public Sdk getSdk() {
|
||||
if (!(myProjectGenerator instanceof PythonProjectGenerator)) return null;
|
||||
if (mySdk != null) return mySdk;
|
||||
if (mySelectedInterpretersPanel != null && mySelectedInterpretersPanel == myNewVirtualEnvPanel) {
|
||||
return new PyLazySdk("Uninitialized virtual environment at " + myNewVirtualEnvPanel.getPath(),
|
||||
() -> myNewVirtualEnvPanel != null ? myNewVirtualEnvPanel.getOrCreateSdk() : null);
|
||||
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
|
||||
if (interpreterPanel == null) return null;
|
||||
final PyAddSdkPanel panel = interpreterPanel.getSelectedPanel();
|
||||
if (panel instanceof PyAddNewVirtualEnvPanel) {
|
||||
final PyAddNewVirtualEnvPanel virtualEnvPanel = (PyAddNewVirtualEnvPanel)panel;
|
||||
return new PyLazySdk("Uninitialized virtual environment at " + virtualEnvPanel.getPath(),
|
||||
virtualEnvPanel::getOrCreateSdk);
|
||||
}
|
||||
else if (panel instanceof PyAddExistingSdkPanel) {
|
||||
return panel.getSdk();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
return (Sdk)mySdkCombo.getComboBox().getSelectedItem();
|
||||
}
|
||||
|
||||
public void setSdk(final Sdk sdk) {
|
||||
mySdk = sdk;
|
||||
@Nullable
|
||||
private Sdk getInterpreterPanelSdk() {
|
||||
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
|
||||
if (interpreterPanel == null) return null;
|
||||
return interpreterPanel.getSdk();
|
||||
}
|
||||
|
||||
public boolean installFramework() {
|
||||
@@ -164,40 +142,10 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
|
||||
((PythonProjectGenerator)myProjectGenerator).locationChanged(PathUtil.getFileName(path));
|
||||
}
|
||||
});
|
||||
|
||||
mySdkCombo.getComboBox().addItemListener(e -> {
|
||||
if (e.getStateChange() == ItemEvent.SELECTED) {
|
||||
final Runnable checkValidOnSwing = () -> ApplicationManager.getApplication().invokeLater(this::checkValid);
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
|
||||
try {
|
||||
// Refresh before validation to make sure no stale data
|
||||
final Sdk sdk = getSdk();
|
||||
if (sdk == null) {
|
||||
return;
|
||||
}
|
||||
final boolean noPackages = PyPackageManager.getInstance(sdk).refreshAndGetPackages(true).isEmpty();
|
||||
if (noPackages) {
|
||||
LOGGER.warn(String.format("No packages on %s", sdk.getHomePath()));
|
||||
}
|
||||
checkValidOnSwing.run();
|
||||
}
|
||||
catch (final ExecutionException exception) {
|
||||
LOGGER.warn(exception);
|
||||
checkValidOnSwing.run();
|
||||
}
|
||||
}, "Refreshing List of Packages, Please Wait", false, null);
|
||||
}
|
||||
});
|
||||
|
||||
if (myRemotePathField != null) {
|
||||
myRemotePathField.addTextChangeListener(this::checkValid);
|
||||
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
|
||||
if (interpreterPanel != null) {
|
||||
UiNotifyConnector.doWhenFirstShown(interpreterPanel, this::checkValid);
|
||||
}
|
||||
|
||||
if (myNewVirtualEnvPanel != null) {
|
||||
myNewVirtualEnvPanel.addChangeListener(this::checkValid);
|
||||
}
|
||||
|
||||
UiNotifyConnector.doWhenFirstShown(mySdkCombo, this::checkValid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,8 +154,11 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
|
||||
*/
|
||||
@Nullable
|
||||
final String getRemotePath() {
|
||||
final PyRemotePathField field = myRemotePathField;
|
||||
return (field != null ? field.getTextField().getText() : null);
|
||||
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
|
||||
if (interpreterPanel == null) return null;
|
||||
final PyAddExistingSdkPanel panel = ObjectUtils.tryCast(interpreterPanel.getSelectedPanel(), PyAddExistingSdkPanel.class);
|
||||
if (panel == null) return null;
|
||||
return panel.getRemotePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -226,85 +177,86 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
|
||||
return false;
|
||||
}
|
||||
|
||||
if (myNewVirtualEnvPanel != null) {
|
||||
final List<ValidationInfo> validationInfos = myNewVirtualEnvPanel.validateAll();
|
||||
final PyAddSdkGroupPanel interpreterPanel = myInterpreterPanel;
|
||||
if (interpreterPanel != null) {
|
||||
final List<ValidationInfo> validationInfos = interpreterPanel.validateAll();
|
||||
if (!validationInfos.isEmpty()) {
|
||||
setErrorText(StringUtil.join(validationInfos, info -> info.message, "\n"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (myProjectGenerator instanceof PythonProjectGenerator) {
|
||||
final Sdk sdk = getSdk();
|
||||
if (sdk == null) {
|
||||
setErrorText("No Python interpreter selected");
|
||||
return false;
|
||||
}
|
||||
if (!(sdk instanceof PyLazySdk) && PythonSdkType.isInvalid(sdk)) {
|
||||
setErrorText("Choose valid python interpreter");
|
||||
return false;
|
||||
}
|
||||
final List<String> warningList = new ArrayList<>();
|
||||
final boolean isPy3k = PythonSdkType.getLanguageLevelForSdk(sdk).isPy3K();
|
||||
try {
|
||||
acceptsSdk(myProjectGenerator, sdk, new File(myLocationField.getText()));
|
||||
}
|
||||
catch (final PythonProjectGenerator.PyNoProjectAllowedOnSdkException e) {
|
||||
setErrorText(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
if (myRemotePathRequired && StringUtil.isEmpty(myRemotePathField.getTextField().getText())) {
|
||||
setErrorText("Remote path not provided");
|
||||
final PythonProjectGenerator generator = ObjectUtils.tryCast(myProjectGenerator, PythonProjectGenerator.class);
|
||||
final Sdk sdk = getInterpreterPanelSdk();
|
||||
|
||||
if (generator == null || sdk == null) return true;
|
||||
|
||||
try {
|
||||
generator.checkProjectCanBeCreatedOnSdk(sdk, new File(myLocationField.getText()));
|
||||
}
|
||||
catch (final PythonProjectGenerator.PyNoProjectAllowedOnSdkException e) {
|
||||
setErrorText(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
final List<String> warnings = new ArrayList<>();
|
||||
|
||||
final PyFrameworkProjectGenerator frameworkGenerator = ObjectUtils.tryCast(myProjectGenerator, PyFrameworkProjectGenerator.class);
|
||||
|
||||
if (frameworkGenerator != null) {
|
||||
final String python3Error = validateFrameworkSupportsPython3(frameworkGenerator, sdk);
|
||||
if (python3Error != null) {
|
||||
setErrorText(python3Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (myProjectGenerator instanceof PyFrameworkProjectGenerator) {
|
||||
PyFrameworkProjectGenerator frameworkProjectGenerator = (PyFrameworkProjectGenerator)myProjectGenerator;
|
||||
String frameworkName = frameworkProjectGenerator.getFrameworkTitle();
|
||||
// Framework package check may be heavy in case of remote sdk and should not be called on AWT, pretend everything is OK for
|
||||
// remote and check for packages later
|
||||
if (!PythonSdkType.isRemote(sdk)) {
|
||||
final Pair<Boolean, List<String>> validationInfo = validateFramework(frameworkGenerator, sdk);
|
||||
myInstallFramework = validationInfo.first;
|
||||
warnings.addAll(validationInfo.second);
|
||||
|
||||
if (isPy3k && !((PyFrameworkProjectGenerator)myProjectGenerator).supportsPython3()) {
|
||||
setErrorText(frameworkName + " is not supported for the selected interpreter");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PythonSdkType.isRemote(sdk)) {
|
||||
return true;
|
||||
}
|
||||
// All code beyond this line may be heavy in case of remote sdk and should not be called on AWT
|
||||
// pretend everything is ok for remote and check package later
|
||||
|
||||
if (!isFrameworkInstalled(sdk)) {
|
||||
if (PyPackageUtil.packageManagementEnabled(sdk)) {
|
||||
myInstallFramework = true;
|
||||
final List<PyPackage> packages = PyPackageUtil.refreshAndGetPackagesModally(sdk);
|
||||
if (!PyPackageUtil.hasManagement(packages)) {
|
||||
warningList.add("Python packaging tools and " + frameworkName + " will be installed on the selected interpreter");
|
||||
}
|
||||
else {
|
||||
warningList.add(frameworkName + " will be installed on the selected interpreter");
|
||||
}
|
||||
}
|
||||
else {
|
||||
warningList.add(frameworkName + " is not installed on the selected interpreter");
|
||||
}
|
||||
}
|
||||
final ValidationResult warningResult = ((PythonProjectGenerator)myProjectGenerator).warningValidation(sdk);
|
||||
if (!warningResult.isOk()) {
|
||||
warningList.add(warningResult.getErrorMessage());
|
||||
}
|
||||
|
||||
if (!warningList.isEmpty()) {
|
||||
final String warning = StringUtil.join(warningList, "<br/>");
|
||||
setWarningText(warning);
|
||||
warnings.add(warningResult.getErrorMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!warnings.isEmpty()) {
|
||||
setWarningText(StringUtil.join(warnings, "<br/>"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isFrameworkInstalled(Sdk sdk) {
|
||||
PyFrameworkProjectGenerator projectGenerator = (PyFrameworkProjectGenerator)getProjectGenerator();
|
||||
return projectGenerator != null && projectGenerator.isFrameworkInstalled(sdk);
|
||||
private static String validateFrameworkSupportsPython3(@NotNull PyFrameworkProjectGenerator generator, @NotNull Sdk sdk) {
|
||||
final String frameworkName = generator.getFrameworkTitle();
|
||||
final boolean isPy3k = PythonSdkType.getLanguageLevelForSdk(sdk).isPy3K();
|
||||
return isPy3k && !generator.supportsPython3() ? frameworkName + " is not supported for the selected interpreter" : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Pair<Boolean, List<String>> validateFramework(@NotNull PyFrameworkProjectGenerator generator, @NotNull Sdk sdk) {
|
||||
final List<String> warnings = new ArrayList<>();
|
||||
boolean installFramework = false;
|
||||
if (!generator.isFrameworkInstalled(sdk)) {
|
||||
final String frameworkName = generator.getFrameworkTitle();
|
||||
if (PyPackageUtil.packageManagementEnabled(sdk)) {
|
||||
installFramework = true;
|
||||
final List<PyPackage> packages = PyPackageUtil.refreshAndGetPackagesModally(sdk);
|
||||
if (!PyPackageUtil.hasManagement(packages)) {
|
||||
warnings.add("Python packaging tools and " + frameworkName + " will be installed on the selected interpreter");
|
||||
}
|
||||
else {
|
||||
warnings.add(frameworkName + " will be installed on the selected interpreter");
|
||||
}
|
||||
}
|
||||
else {
|
||||
warnings.add(frameworkName + " is not installed on the selected interpreter");
|
||||
}
|
||||
}
|
||||
return Pair.create(installFramework, warnings);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -321,14 +273,6 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
|
||||
panel.add(locationPanel);
|
||||
panel.add(createInterpretersPanel());
|
||||
|
||||
final PythonRemoteInterpreterManager remoteInterpreterManager = PythonRemoteInterpreterManager.getInstance();
|
||||
|
||||
final Sdk sdk = getSdk();
|
||||
if (remoteInterpreterManager != null && sdk != null) {
|
||||
createRemotePathField(panel, remoteInterpreterManager);
|
||||
}
|
||||
|
||||
|
||||
final JPanel basePanelExtension = ((PythonProjectGenerator)myProjectGenerator).extendBasePanel();
|
||||
if (basePanelExtension != null) {
|
||||
panel.add(basePanelExtension);
|
||||
@@ -343,175 +287,59 @@ public class ProjectSpecificSettingsStep<T> extends ProjectSettingsStepBase<T> i
|
||||
private JPanel createInterpretersPanel() {
|
||||
final JPanel container = new JPanel(new BorderLayout());
|
||||
final JPanel decoratorPanel = new JPanel(new VerticalFlowLayout());
|
||||
final HideableDecorator decorator = new HideableDecorator(decoratorPanel, "Project Interpreter", false);
|
||||
|
||||
final List<Sdk> existingSdks = getValidPythonSdks();
|
||||
final Sdk preferredSdk = getPreferredSdk(existingSdks);
|
||||
|
||||
final String newProjectPath = myLocationField.getText().trim();
|
||||
final PyAddNewVirtualEnvPanel newVirtualEnvPanel = new PyAddNewVirtualEnvPanel(null, existingSdks, newProjectPath);
|
||||
final PyAddExistingSdkPanel existingSdkPanel = new PyAddExistingSdkPanel(null, existingSdks, newProjectPath, preferredSdk);
|
||||
|
||||
final HideableDecorator decorator = new HideableDecorator(decoratorPanel, getProjectInterpreterTitle(newVirtualEnvPanel), false);
|
||||
decorator.setContentComponent(container);
|
||||
final Project defaultProject = ProjectManager.getInstance().getDefaultProject();
|
||||
final List<Sdk> pythonSdks = PyConfigurableInterpreterList.getInstance(defaultProject).getAllPythonSdks();
|
||||
Iterables.removeIf(pythonSdks, sdk -> !(sdk.getSdkType() instanceof PythonSdkType) || PythonSdkType.isInvalid(sdk));
|
||||
Collections.sort(pythonSdks, new PreferredSdkComparator());
|
||||
myNewVirtualEnvPanel = new PyAddNewVirtualEnvPanel(null, pythonSdks, myLocationField.getText().trim());
|
||||
myLocationField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
|
||||
@Override
|
||||
protected void textChanged(DocumentEvent e) {
|
||||
myNewVirtualEnvPanel.setNewProjectPath(myLocationField.getText().trim());
|
||||
|
||||
final List<PyAddSdkPanel> panels = Arrays.asList(newVirtualEnvPanel, existingSdkPanel);
|
||||
myInterpreterPanel = new PyAddSdkGroupPanel("New project interpreter", getIcon(), panels, newVirtualEnvPanel);
|
||||
myInterpreterPanel.addChangeListener(() -> {
|
||||
PyAddSdkGroupPanel panel = myInterpreterPanel;
|
||||
if (panel != null) {
|
||||
decorator.setTitle(getProjectInterpreterTitle(panel.getSelectedPanel()));
|
||||
}
|
||||
});
|
||||
|
||||
final Map<JBRadioButton, JPanel> radioButtons = new LinkedHashMap<>();
|
||||
final JBRadioButton newVirtualEnvButton = new JBRadioButton(myNewVirtualEnvPanel.getPanelName());
|
||||
radioButtons.put(newVirtualEnvButton, myNewVirtualEnvPanel);
|
||||
radioButtons.put(new JBRadioButton("Existing interpreter"), createInterpreterCombo());
|
||||
final ButtonGroup buttonGroup = new ButtonGroup();
|
||||
for (JBRadioButton button : radioButtons.keySet()) {
|
||||
buttonGroup.add(button);
|
||||
}
|
||||
|
||||
final FormBuilder formBuilder = FormBuilder.createFormBuilder();
|
||||
for (Map.Entry<JBRadioButton, JPanel> entry : radioButtons.entrySet()) {
|
||||
final JBRadioButton button = entry.getKey();
|
||||
final JPanel panel = entry.getValue();
|
||||
panel.setBorder(JBUI.Borders.emptyLeft(30));
|
||||
formBuilder.addComponent(button);
|
||||
formBuilder.addComponent(panel);
|
||||
button.addItemListener(e -> {
|
||||
mySelectedInterpretersPanel = panel;
|
||||
for (JPanel p : radioButtons.values()) {
|
||||
final boolean isCurrentPanel = p.equals(panel);
|
||||
UIUtil.setEnabled(p, isCurrentPanel, true);
|
||||
if (isCurrentPanel) {
|
||||
decorator.setTitle("Project Interpreter: " + StringUtil.toTitleCase(button.getText()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mySelectedInterpretersPanel = myNewVirtualEnvPanel;
|
||||
newVirtualEnvButton.setSelected(true);
|
||||
|
||||
container.add(formBuilder.getPanel(), BorderLayout.NORTH);
|
||||
newVirtualEnvPanel.addChangeListener(this::checkValid);
|
||||
existingSdkPanel.addChangeListener(this::checkValid);
|
||||
myInterpreterPanel.addChangeListener(this::checkValid);
|
||||
|
||||
container.add(myInterpreterPanel, BorderLayout.NORTH);
|
||||
return decoratorPanel;
|
||||
}
|
||||
|
||||
private void createRemotePathField(@NotNull final JPanel panelToAddField,
|
||||
@NotNull final PythonRemoteInterpreterManager remoteInterpreterManager) {
|
||||
myRemotePathField = new PyRemotePathField();
|
||||
|
||||
myRemotePathField.addActionListener(e -> {
|
||||
final Sdk currentSdk = getSdk();
|
||||
if (!PySdkUtil.isRemote(currentSdk)) {
|
||||
return;
|
||||
}
|
||||
// If chosen SDK is remote then display
|
||||
|
||||
final Pair<Supplier<String>, JPanel> browserForm;
|
||||
try {
|
||||
browserForm = remoteInterpreterManager.createServerBrowserForm(currentSdk);
|
||||
}
|
||||
catch (final ExecutionException | InterruptedException ex) {
|
||||
Logger.getInstance(ProjectSpecificSettingsStep.class).warn("Failed to create server browse button", ex);
|
||||
JBPopupFactory.getInstance().createMessage("Failed to browse remote server. Make sure you have permissions. ").show(panelToAddField);
|
||||
return;
|
||||
}
|
||||
if (browserForm != null) {
|
||||
browserForm.second.setVisible(true);
|
||||
final DialogWrapper wrapper = new MyRemoteServerBrowserDialog(browserForm.second);
|
||||
if (wrapper.showAndGet()) {
|
||||
myRemotePathField.getTextField().setText(browserForm.first.get());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mySdkCombo.addChangedListener(e -> configureMappingField(remoteInterpreterManager));
|
||||
panelToAddField.add(myRemotePathField.getMainPanel());
|
||||
configureMappingField(remoteInterpreterManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables "remote path" based on interpreter.
|
||||
*/
|
||||
private void configureMappingField(@NotNull final PythonRemoteInterpreterManager remoteInterpreterManager) {
|
||||
if (myRemotePathField == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final JPanel mainPanel = myRemotePathField.getMainPanel();
|
||||
final PyProjectSynchronizer synchronizer = getSynchronizer(remoteInterpreterManager);
|
||||
if (synchronizer != null) {
|
||||
final String defaultRemotePath = synchronizer.getDefaultRemotePath();
|
||||
final boolean mappingRequired = defaultRemotePath != null;
|
||||
mainPanel.setVisible(mappingRequired);
|
||||
final TextAccessor textField = myRemotePathField.getTextField();
|
||||
if (mappingRequired && StringUtil.isEmpty(textField.getText())) {
|
||||
textField.setText(defaultRemotePath);
|
||||
}
|
||||
myRemotePathRequired = mappingRequired;
|
||||
}
|
||||
else {
|
||||
mainPanel.setVisible(false);
|
||||
myRemotePathRequired = false;
|
||||
}
|
||||
@NotNull
|
||||
private static String getProjectInterpreterTitle(@NotNull PyAddSdkPanel panel) {
|
||||
return "Project Interpreter: " + StringUtil.toTitleCase(panel.getPanelName());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PyProjectSynchronizer getSynchronizer(@NotNull final PythonRemoteInterpreterManager manager) {
|
||||
final Sdk sdk = getSdk();
|
||||
if (sdk == null) {
|
||||
return null;
|
||||
private Sdk getPreferredSdk(@NotNull List<Sdk> sdks) {
|
||||
final PyFrameworkProjectGenerator projectGenerator = ObjectUtils.tryCast(getProjectGenerator(), PyFrameworkProjectGenerator.class);
|
||||
final boolean onlyPython2 = projectGenerator != null && !projectGenerator.supportsPython3();
|
||||
final Sdk preferred = ContainerUtil.getFirstItem(sdks);
|
||||
if (preferred == null) return null;
|
||||
if (onlyPython2 && PythonSdkType.getLanguageLevelForSdk(preferred).isAtLeast(LanguageLevel.PYTHON30)) {
|
||||
final Sdk python2Sdk = PythonSdkType.findPython2Sdk(sdks);
|
||||
return python2Sdk != null ? python2Sdk : preferred;
|
||||
}
|
||||
return manager.getSynchronizer(sdk);
|
||||
return preferred;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private PythonSdkChooserCombo createInterpreterCombo() {
|
||||
final Project project = ProjectManager.getInstance().getDefaultProject();
|
||||
final List<Sdk> sdks = PyConfigurableInterpreterList.getInstance(project).getAllPythonSdks();
|
||||
VirtualEnvProjectFilter.removeAllAssociated(sdks);
|
||||
Iterables.removeIf(sdks, sdk -> sdk != null && PythonSdkType.isInvalid(sdk));
|
||||
Sdk compatibleSdk = sdks.isEmpty() ? null : sdks.iterator().next();
|
||||
DirectoryProjectGenerator generator = getProjectGenerator();
|
||||
if (generator instanceof PyFrameworkProjectGenerator && !((PyFrameworkProjectGenerator)generator).supportsPython3()) {
|
||||
if (compatibleSdk != null && PythonSdkType.getLanguageLevelForSdk(compatibleSdk).isPy3K()) {
|
||||
Sdk python2Sdk = PythonSdkType.findPython2Sdk(sdks);
|
||||
if (python2Sdk != null) {
|
||||
compatibleSdk = python2Sdk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Sdk preferred = compatibleSdk;
|
||||
mySdkCombo = new PythonSdkChooserCombo(project, sdks, myLocationField.getText().trim(), sdk -> sdk == preferred);
|
||||
if (SystemInfo.isMac && !UIUtil.isUnderDarcula()) {
|
||||
mySdkCombo.putClientProperty("JButton.buttonType", null);
|
||||
}
|
||||
mySdkCombo.setButtonIcon(PythonIcons.Python.InterpreterGear);
|
||||
myLocationField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
|
||||
@Override
|
||||
protected void textChanged(DocumentEvent e) {
|
||||
mySdkCombo.setNewProjectPath(myLocationField.getText().trim());
|
||||
}
|
||||
});
|
||||
|
||||
return mySdkCombo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog to display remote server browser
|
||||
*/
|
||||
private static class MyRemoteServerBrowserDialog extends DialogWrapper {
|
||||
|
||||
private final JPanel myBrowserForm;
|
||||
|
||||
MyRemoteServerBrowserDialog(@NotNull final JPanel browserForm) {
|
||||
super(true);
|
||||
myBrowserForm = browserForm;
|
||||
init();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myBrowserForm;
|
||||
}
|
||||
private static List<Sdk> getValidPythonSdks() {
|
||||
final List<Sdk> pythonSdks = PyConfigurableInterpreterList.getInstance(null).getAllPythonSdks();
|
||||
Iterables.removeIf(pythonSdks, sdk -> !(sdk.getSdkType() instanceof PythonSdkType) ||
|
||||
PythonSdkType.isInvalid(sdk) ||
|
||||
PySdkExtKt.getAssociatedProjectPath(sdk) != null);
|
||||
Collections.sort(pythonSdks, new PreferredSdkComparator());
|
||||
return pythonSdks;
|
||||
}
|
||||
}
|
||||
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
// Copyright 2000-2017 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.newProject.steps
|
||||
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.openapi.ui.ValidationInfo
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.component1
|
||||
import com.intellij.openapi.util.component2
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.util.ui.FormBuilder
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.jetbrains.python.remote.PyProjectSynchronizer
|
||||
import com.jetbrains.python.remote.PythonRemoteInterpreterManager
|
||||
import com.jetbrains.python.sdk.PySdkUtil
|
||||
import com.jetbrains.python.sdk.PythonSdkType
|
||||
import com.jetbrains.python.sdk.add.PyAddSdkPanel
|
||||
import icons.PythonIcons
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Component
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
class PyAddExistingSdkPanel(project: Project?,
|
||||
existingSdks: List<Sdk>,
|
||||
newProjectPath: String?,
|
||||
preferredSdk: Sdk?) : PyAddSdkPanel() {
|
||||
|
||||
override val panelName = "Existing interpreter"
|
||||
|
||||
override val sdk: Sdk?
|
||||
get() = sdkChooserCombo.comboBox.selectedItem as? Sdk
|
||||
|
||||
val remotePath: String?
|
||||
get() = if (remotePathField.mainPanel.isVisible) remotePathField.textField.text else null
|
||||
|
||||
private val sdkChooserCombo: PythonSdkChooserCombo
|
||||
private val remotePathField = PyRemotePathField().apply {
|
||||
addActionListener {
|
||||
val currentSdk = sdk ?: return@addActionListener
|
||||
if (!PySdkUtil.isRemote(currentSdk)) return@addActionListener
|
||||
textField.text = currentSdk.chooseRemotePath(parent) ?: return@addActionListener
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
layout = BorderLayout()
|
||||
// TODO: Set new project path on external change
|
||||
sdkChooserCombo = PythonSdkChooserCombo(project, existingSdks, newProjectPath, { it != null && it == preferredSdk }).apply {
|
||||
if (SystemInfo.isMac && !UIUtil.isUnderDarcula()) {
|
||||
putClientProperty("JButton.buttonType", null)
|
||||
}
|
||||
setButtonIcon(PythonIcons.Python.InterpreterGear)
|
||||
addChangedListener {
|
||||
update()
|
||||
}
|
||||
}
|
||||
val formPanel = FormBuilder.createFormBuilder()
|
||||
.addLabeledComponent("Interpreter:", sdkChooserCombo)
|
||||
.addComponent(remotePathField.mainPanel)
|
||||
.panel
|
||||
add(formPanel, BorderLayout.NORTH)
|
||||
update()
|
||||
}
|
||||
|
||||
override fun validateAll() =
|
||||
listOf(validateSdkChooserField(),
|
||||
validateRemotePathField())
|
||||
.filterNotNull()
|
||||
|
||||
fun addChangeListener(listener: Runnable) {
|
||||
sdkChooserCombo.addChangedListener { listener.run() }
|
||||
remotePathField.addTextChangeListener { listener.run() }
|
||||
}
|
||||
|
||||
private fun validateSdkChooserField(): ValidationInfo? {
|
||||
val selectedSdk = sdk
|
||||
val message = when {
|
||||
selectedSdk == null -> "No Python interpreter selected"
|
||||
PythonSdkType.isInvalid(selectedSdk) -> "Choose valid Python interpreter"
|
||||
else -> return null
|
||||
}
|
||||
return ValidationInfo(message, sdkChooserCombo)
|
||||
}
|
||||
|
||||
private fun validateRemotePathField(): ValidationInfo? {
|
||||
val path = remotePath
|
||||
return when {
|
||||
path != null && path.isBlank() -> ValidationInfo("Remote path not provided")
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun update() {
|
||||
val synchronizer = sdk?.projectSynchronizer
|
||||
remotePathField.mainPanel.isVisible = synchronizer != null
|
||||
if (synchronizer != null) {
|
||||
val defaultRemotePath = synchronizer.getDefaultRemotePath()
|
||||
val textField = remotePathField.textField
|
||||
if (defaultRemotePath != null && StringUtil.isEmpty(textField.text)) {
|
||||
textField.text = defaultRemotePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val Sdk.projectSynchronizer: PyProjectSynchronizer?
|
||||
get() = PythonRemoteInterpreterManager.getInstance()?.getSynchronizer(this)
|
||||
|
||||
private fun Sdk.chooseRemotePath(owner: Component): String? {
|
||||
val remoteManager = PythonRemoteInterpreterManager.getInstance() ?: return null
|
||||
val (supplier, panel) = try {
|
||||
remoteManager.createServerBrowserForm(this) ?: return null
|
||||
}
|
||||
catch (e: Exception) {
|
||||
when (e) {
|
||||
is ExecutionException, is InterruptedException -> {
|
||||
Logger.getInstance(PyAddExistingSdkPanel::class.java).warn("Failed to create server browse button", e)
|
||||
JBPopupFactory.getInstance()
|
||||
.createMessage("Failed to browse remote server. Make sure you have permissions.")
|
||||
.show(owner)
|
||||
return null
|
||||
}
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
panel.isVisible = true
|
||||
val wrapper = object : DialogWrapper(true) {
|
||||
init { init() }
|
||||
override fun createCenterPanel() = panel
|
||||
}
|
||||
return if (wrapper.showAndGet()) supplier.get() else null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.jetbrains.python.sdk.add
|
||||
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.ui.components.JBRadioButton
|
||||
import com.intellij.util.ui.FormBuilder
|
||||
import com.intellij.util.ui.JBUI
|
||||
@@ -33,7 +34,8 @@ class PyAddSdkGroupPanel(name: String,
|
||||
defaultPanel: PyAddSdkPanel) : PyAddSdkPanel() {
|
||||
override val panelName = name
|
||||
override val icon = panelIcon
|
||||
private var selectedPanel: PyAddSdkPanel? = null
|
||||
var selectedPanel: PyAddSdkPanel? = null
|
||||
private val changeListeners: MutableList<Runnable> = mutableListOf()
|
||||
|
||||
init {
|
||||
layout = BorderLayout()
|
||||
@@ -46,29 +48,41 @@ class PyAddSdkGroupPanel(name: String,
|
||||
|
||||
override fun validateAll() = panels.filter { it.isEnabled }.flatMap { it.validateAll() }
|
||||
|
||||
override val sdk: Sdk?
|
||||
get() = selectedPanel?.sdk
|
||||
|
||||
override fun getOrCreateSdk() = selectedPanel?.getOrCreateSdk()
|
||||
|
||||
fun addChangeListener(listener: Runnable) {
|
||||
changeListeners += listener
|
||||
}
|
||||
|
||||
private fun createRadioButtonPanel(panels: List<PyAddSdkPanel>, defaultPanel: PyAddSdkPanel): JPanel? {
|
||||
val radioButtons = panels.map { JBRadioButton(it.panelName) to it }.toMap(linkedMapOf())
|
||||
val buttonMap = panels.map { JBRadioButton(it.panelName) to it }.toMap(linkedMapOf())
|
||||
ButtonGroup().apply {
|
||||
for (button in radioButtons.keys) {
|
||||
for (button in buttonMap.keys) {
|
||||
add(button)
|
||||
}
|
||||
}
|
||||
val formBuilder = FormBuilder.createFormBuilder()
|
||||
for ((button, panel) in radioButtons) {
|
||||
for ((button, panel) in buttonMap) {
|
||||
panel.border = JBUI.Borders.emptyLeft(30)
|
||||
formBuilder.addComponent(button)
|
||||
formBuilder.addComponent(panel)
|
||||
button.addItemListener {
|
||||
selectedPanel = panel
|
||||
for (c in panels) {
|
||||
UIUtil.setEnabled(c, c == panel, true)
|
||||
}
|
||||
if (button.isSelected) {
|
||||
selectedPanel = panel
|
||||
for (listener in changeListeners) {
|
||||
listener.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
selectedPanel = defaultPanel
|
||||
radioButtons.filterValues { it == defaultPanel }.keys.first().isSelected = true
|
||||
buttonMap.filterValues { it == defaultPanel }.keys.first().isSelected = true
|
||||
return formBuilder.panel
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,9 @@ import javax.swing.JPanel
|
||||
abstract class PyAddSdkPanel : JPanel() {
|
||||
abstract val panelName: String
|
||||
open val icon: Icon = PythonIcons.Python.Python
|
||||
open val sdk: Sdk? = null
|
||||
|
||||
open fun getOrCreateSdk(): Sdk? = null
|
||||
open fun getOrCreateSdk(): Sdk? = sdk
|
||||
|
||||
open fun validateAll(): List<ValidationInfo> = emptyList()
|
||||
|
||||
@@ -47,9 +48,9 @@ abstract class PyAddSdkPanel : JPanel() {
|
||||
StringUtil.isEmptyOrSpaces(text) -> "Location field is empty"
|
||||
file.exists() && !file.isDirectory -> "Location field path is not a directory"
|
||||
file.isNotEmptyDirectory -> "Location directory is not empty"
|
||||
else -> null
|
||||
else -> return null
|
||||
}
|
||||
return message?.let { ValidationInfo(it, field) }
|
||||
return ValidationInfo(message, field)
|
||||
}
|
||||
|
||||
@JvmStatic protected fun validateAnacondaPresense(component: JComponent?): ValidationInfo? =
|
||||
|
||||
Reference in New Issue
Block a user