mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
PY-52925: Fix file browsing for WSL and other remote targets
User should have ability to browse target, not to type path manually GitOrigin-RevId: b309e0683d0bc2ab3bfe1444ce945cb235d9fb14
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3dd5b1ed3e
commit
906a3a598c
@@ -16,19 +16,24 @@ import java.util.function.Supplier
|
|||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See [BrowsableTargetEnvironmentType.createBrowser]
|
||||||
|
*/
|
||||||
@Deprecated("Use overloaded method with Kotlin UI DSL 2 API")
|
@Deprecated("Use overloaded method with Kotlin UI DSL 2 API")
|
||||||
fun textFieldWithBrowseTargetButton(row: Row,
|
fun textFieldWithBrowseTargetButton(row: Row,
|
||||||
targetType: BrowsableTargetEnvironmentType,
|
targetType: BrowsableTargetEnvironmentType,
|
||||||
targetSupplier: Supplier<out TargetEnvironmentConfiguration>,
|
targetSupplier: Supplier<out TargetEnvironmentConfiguration>,
|
||||||
project: Project,
|
project: Project,
|
||||||
@NlsContexts.DialogTitle title: String,
|
@NlsContexts.DialogTitle title: String,
|
||||||
property: PropertyBinding<String>): CellBuilder<TextFieldWithBrowseButton> {
|
property: PropertyBinding<String>,
|
||||||
|
noLocalFs: Boolean = false): CellBuilder<TextFieldWithBrowseButton> {
|
||||||
val textFieldWithBrowseButton = TextFieldWithBrowseButton()
|
val textFieldWithBrowseButton = TextFieldWithBrowseButton()
|
||||||
val browser = targetType.createBrowser(project,
|
val browser = targetType.createBrowser(project,
|
||||||
title,
|
title,
|
||||||
TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT,
|
TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT,
|
||||||
textFieldWithBrowseButton.textField,
|
textFieldWithBrowseButton.textField,
|
||||||
targetSupplier)
|
targetSupplier,
|
||||||
|
noLocalFs)
|
||||||
textFieldWithBrowseButton.addActionListener(browser)
|
textFieldWithBrowseButton.addActionListener(browser)
|
||||||
textFieldWithBrowseButton.text = property.get()
|
textFieldWithBrowseButton.text = property.get()
|
||||||
return row.component(textFieldWithBrowseButton).withBinding(TextFieldWithBrowseButton::getText,
|
return row.component(textFieldWithBrowseButton).withBinding(TextFieldWithBrowseButton::getText,
|
||||||
@@ -36,17 +41,21 @@ fun textFieldWithBrowseTargetButton(row: Row,
|
|||||||
property)
|
property)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See [BrowsableTargetEnvironmentType.createBrowser]
|
||||||
|
*/
|
||||||
fun com.intellij.ui.dsl.builder.Row.textFieldWithBrowseTargetButton(targetType: BrowsableTargetEnvironmentType,
|
fun com.intellij.ui.dsl.builder.Row.textFieldWithBrowseTargetButton(targetType: BrowsableTargetEnvironmentType,
|
||||||
targetSupplier: Supplier<out TargetEnvironmentConfiguration>,
|
targetSupplier: Supplier<out TargetEnvironmentConfiguration>,
|
||||||
project: Project,
|
project: Project,
|
||||||
@NlsContexts.DialogTitle title: String,
|
@NlsContexts.DialogTitle title: String,
|
||||||
property: MutableProperty<String>): Cell<TextFieldWithBrowseButton> {
|
property: MutableProperty<String>): Cell<TextFieldWithBrowseButton> {
|
||||||
val textFieldWithBrowseButton = TextFieldWithBrowseButton()
|
val textFieldWithBrowseButton = TextFieldWithBrowseButton()
|
||||||
val browser = targetType.createBrowser(project,
|
val browser = targetType.createBrowser(project,
|
||||||
title,
|
title,
|
||||||
TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT,
|
TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT,
|
||||||
textFieldWithBrowseButton.textField,
|
textFieldWithBrowseButton.textField,
|
||||||
targetSupplier)
|
targetSupplier,
|
||||||
|
false)
|
||||||
textFieldWithBrowseButton.addActionListener(browser)
|
textFieldWithBrowseButton.addActionListener(browser)
|
||||||
return cell(textFieldWithBrowseButton)
|
return cell(textFieldWithBrowseButton)
|
||||||
.bind(TextFieldWithBrowseButton::getText, TextFieldWithBrowseButton::setText, property)
|
.bind(TextFieldWithBrowseButton::getText, TextFieldWithBrowseButton::setText, property)
|
||||||
|
|||||||
@@ -66,14 +66,15 @@ class WslTargetType : TargetEnvironmentType<WslTargetEnvironmentConfiguration>(T
|
|||||||
title: String?,
|
title: String?,
|
||||||
textComponentAccessor: TextComponentAccessor<T>,
|
textComponentAccessor: TextComponentAccessor<T>,
|
||||||
component: T,
|
component: T,
|
||||||
configurationSupplier: Supplier<out TargetEnvironmentConfiguration>): ActionListener = ActionListener {
|
configurationSupplier: Supplier<out TargetEnvironmentConfiguration>,
|
||||||
|
noLocalFs: Boolean): ActionListener = ActionListener {
|
||||||
val configuration = configurationSupplier.get()
|
val configuration = configurationSupplier.get()
|
||||||
if (configuration is WslTargetEnvironmentConfiguration) {
|
if (configuration is WslTargetEnvironmentConfiguration) {
|
||||||
configuration.distribution?.let {
|
configuration.distribution?.let {
|
||||||
WslPathBrowser(object : TextAccessor {
|
WslPathBrowser(object : TextAccessor {
|
||||||
override fun setText(text: String) = textComponentAccessor.setText(component, text)
|
override fun setText(text: String) = textComponentAccessor.setText(component, text)
|
||||||
override fun getText() = textComponentAccessor.getText(component)
|
override fun getText() = textComponentAccessor.getText(component)
|
||||||
}).browsePath(it, component)
|
}).browsePath(it, component, accessWindowsFs = !noLocalFs)
|
||||||
return@ActionListener
|
return@ActionListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,31 @@ import java.awt.*;
|
|||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment type provides access to its filesystem for services like {@link com.intellij.openapi.ui.TextFieldWithBrowseButton}
|
||||||
|
* So you can browse (possibily remote) target.
|
||||||
|
*/
|
||||||
public interface BrowsableTargetEnvironmentType {
|
public interface BrowsableTargetEnvironmentType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param textComponentAccessor where path should be set. See {@link TextComponentAccessor#TEXT_FIELD_WHOLE_TEXT}
|
||||||
|
* @param component text field component
|
||||||
|
* @param configurationSupplier returns environment configuration
|
||||||
|
* @param noLocalFs some targets (WSL is the only known for now) may provide access to the local filesystem.
|
||||||
|
* True means you do not need it
|
||||||
|
* @return Action listener should be installed on "browse" button you want to show target FS browser.
|
||||||
|
*/
|
||||||
@NotNull <T extends Component> ActionListener createBrowser(@NotNull Project project,
|
@NotNull <T extends Component> ActionListener createBrowser(@NotNull Project project,
|
||||||
@NlsContexts.DialogTitle String title,
|
@NlsContexts.DialogTitle String title,
|
||||||
@NotNull TextComponentAccessor<T> textComponentAccessor,
|
@NotNull TextComponentAccessor<T> textComponentAccessor,
|
||||||
@NotNull T component,
|
@NotNull T component,
|
||||||
@NotNull Supplier<? extends TargetEnvironmentConfiguration> configurationSupplier);
|
@NotNull Supplier<? extends TargetEnvironmentConfiguration> configurationSupplier,
|
||||||
|
boolean noLocalFs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When configurable contains both connection parameters and components using them (text fields with browsing in this case),
|
* When configurable contains both connection parameters and components using them (text fields with browsing in this case),
|
||||||
* those components need to have current connection settings available, not the last applied to with [Configurable.apply].
|
* those components need to have current connection settings available, not the last applied to with [Configurable.apply].
|
||||||
*
|
* <p>
|
||||||
* This interface displays ability and provides API to get connection settings, which are shown in UI. See IDEA-255466.
|
* This interface displays ability and provides API to get connection settings, which are shown in UI. See IDEA-255466.
|
||||||
*/
|
*/
|
||||||
interface ConfigurableCurrentConfigurationProvider {
|
interface ConfigurableCurrentConfigurationProvider {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class GradleRuntimeTargetUI<C : TargetEnvironmentConfiguration>(private val conf
|
|||||||
val configuration = configurationProvider.environmentConfiguration
|
val configuration = configurationProvider.environmentConfiguration
|
||||||
val targetType = configuration.getTargetType() as? BrowsableTargetEnvironmentType ?: break
|
val targetType = configuration.getTargetType() as? BrowsableTargetEnvironmentType ?: break
|
||||||
addTargetActionListener(configurationProvider.pathMapper,
|
addTargetActionListener(configurationProvider.pathMapper,
|
||||||
targetType.createBrowser(project, title, TEXT_FIELD_WHOLE_TEXT, textField) { configuration })
|
targetType.createBrowser(project, title, TEXT_FIELD_WHOLE_TEXT, textField, { configuration }, false))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
// Copyright 2000-2021 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.
|
// Copyright 2000-2021 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.configuration;
|
package com.jetbrains.python.configuration;
|
||||||
|
|
||||||
|
import com.intellij.execution.target.BrowsableTargetEnvironmentType;
|
||||||
|
import com.intellij.execution.target.TargetBasedSdkAdditionalData;
|
||||||
|
import com.intellij.execution.target.TargetEnvironmentConfigurationKt;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.projectRoots.SdkAdditionalData;
|
||||||
import com.intellij.openapi.projectRoots.SdkModificator;
|
import com.intellij.openapi.projectRoots.SdkModificator;
|
||||||
import com.intellij.openapi.ui.DialogWrapper;
|
import com.intellij.openapi.ui.DialogWrapper;
|
||||||
|
import com.intellij.openapi.ui.TextComponentAccessor;
|
||||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||||
|
import com.intellij.openapi.util.NlsContexts;
|
||||||
import com.intellij.openapi.util.NlsSafe;
|
import com.intellij.openapi.util.NlsSafe;
|
||||||
import com.intellij.openapi.util.io.FileUtil;
|
import com.intellij.openapi.util.io.FileUtil;
|
||||||
import com.intellij.ui.ClickListener;
|
import com.intellij.ui.ClickListener;
|
||||||
@@ -19,9 +25,11 @@ import com.jetbrains.python.sdk.flavors.CondaEnvSdkFlavor;
|
|||||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
|
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
|
||||||
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor;
|
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
|
|
||||||
|
|
||||||
@@ -48,12 +56,20 @@ public class EditSdkDialog extends DialogWrapper {
|
|||||||
});
|
});
|
||||||
final String homePath = sdk.getHomePath();
|
final String homePath = sdk.getHomePath();
|
||||||
myInterpreterPathTextField.setText(homePath);
|
myInterpreterPathTextField.setText(homePath);
|
||||||
myInterpreterPathTextField.addBrowseFolderListener(PyBundle.message("sdk.edit.dialog.specify.interpreter.path"), null, project,
|
var sdkAdditionalData = sdk.getSdkAdditionalData();
|
||||||
PythonSdkType.getInstance().getHomeChooserDescriptor());
|
var label = PyBundle.message("sdk.edit.dialog.specify.interpreter.path");
|
||||||
|
var targetListener = createBrowseTargetListener(project, sdkAdditionalData, label);
|
||||||
|
if (targetListener != null) {
|
||||||
|
myInterpreterPathTextField.addActionListener(targetListener);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
myInterpreterPathTextField.addBrowseFolderListener(label, null, project,
|
||||||
|
PythonSdkType.getInstance().getHomeChooserDescriptor());
|
||||||
|
}
|
||||||
myRemoveAssociationLabel.setVisible(false);
|
myRemoveAssociationLabel.setVisible(false);
|
||||||
final PythonSdkFlavor sdkFlavor = PythonSdkFlavor.getPlatformIndependentFlavor(homePath);
|
final PythonSdkFlavor sdkFlavor = PythonSdkFlavor.getPlatformIndependentFlavor(homePath);
|
||||||
if ((sdkFlavor instanceof VirtualEnvSdkFlavor) || (sdkFlavor instanceof CondaEnvSdkFlavor)) {
|
if ((sdkFlavor instanceof VirtualEnvSdkFlavor) || (sdkFlavor instanceof CondaEnvSdkFlavor)) {
|
||||||
PythonSdkAdditionalData data = (PythonSdkAdditionalData) sdk.getSdkAdditionalData();
|
PythonSdkAdditionalData data = (PythonSdkAdditionalData)sdk.getSdkAdditionalData();
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
final String path = data.getAssociatedModulePath();
|
final String path = data.getAssociatedModulePath();
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
@@ -86,6 +102,25 @@ public class EditSdkDialog extends DialogWrapper {
|
|||||||
}.installOn(myRemoveAssociationLabel);
|
}.installOn(myRemoveAssociationLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return action to call when user clicks on "browse" button if sdk is target SDK that supports browsing
|
||||||
|
*/
|
||||||
|
private @Nullable ActionListener createBrowseTargetListener(@NotNull Project project, @NotNull SdkAdditionalData sdkAdditionalData,
|
||||||
|
@NotNull @NlsContexts.DialogTitle String label) {
|
||||||
|
if (!(sdkAdditionalData instanceof TargetBasedSdkAdditionalData)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var configuration = ((TargetBasedSdkAdditionalData)sdkAdditionalData).getTargetEnvironmentConfiguration();
|
||||||
|
if (configuration != null) {
|
||||||
|
var type = TargetEnvironmentConfigurationKt.getTargetType(configuration);
|
||||||
|
if (type instanceof BrowsableTargetEnvironmentType) {
|
||||||
|
return ((BrowsableTargetEnvironmentType)type).createBrowser(project, label, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT,
|
||||||
|
myInterpreterPathTextField.getTextField(), () -> configuration, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected JComponent createCenterPanel() {
|
protected JComponent createCenterPanel() {
|
||||||
return myMainPanel;
|
return myMainPanel;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import com.jetbrains.python.sdk.*;
|
|||||||
import com.jetbrains.python.sdk.add.PyAddSdkDialog;
|
import com.jetbrains.python.sdk.add.PyAddSdkDialog;
|
||||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
|
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
|
||||||
import com.jetbrains.python.target.PyTargetAwareAdditionalData;
|
import com.jetbrains.python.target.PyTargetAwareAdditionalData;
|
||||||
import com.jetbrains.python.ui.ManualPathEntryDialog;
|
import com.jetbrains.python.ui.remotePathEditor.ManualPathEntryDialog;
|
||||||
import one.util.streamex.StreamEx;
|
import one.util.streamex.StreamEx;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -554,8 +554,8 @@ public class PythonSdkDetailsDialog extends DialogWrapper {
|
|||||||
return remoteInterpreterManager.chooseRemoteFiles(myProject, (PyRemoteSdkAdditionalDataBase)sdkAdditionalData, false);
|
return remoteInterpreterManager.chooseRemoteFiles(myProject, (PyRemoteSdkAdditionalDataBase)sdkAdditionalData, false);
|
||||||
}
|
}
|
||||||
else if (sdkAdditionalData instanceof PyTargetAwareAdditionalData) {
|
else if (sdkAdditionalData instanceof PyTargetAwareAdditionalData) {
|
||||||
// TODO [targets] Use proper file chooser dialog for corresponding target
|
var dialog = new ManualPathEntryDialog(myProject, Platform.UNIX,
|
||||||
ManualPathEntryDialog dialog = new ManualPathEntryDialog(myProject, Platform.UNIX);
|
((PyTargetAwareAdditionalData)sdkAdditionalData).getTargetEnvironmentConfiguration());
|
||||||
if (dialog.showAndGet()) {
|
if (dialog.showAndGet()) {
|
||||||
return new String[]{dialog.getPath()};
|
return new String[]{dialog.getPath()};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -643,4 +643,3 @@ public final class PythonSdkType extends SdkType {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import com.intellij.util.PathUtil
|
|||||||
import com.jetbrains.python.PyBundle
|
import com.jetbrains.python.PyBundle
|
||||||
import com.jetbrains.python.sdk.PythonSdkType
|
import com.jetbrains.python.sdk.PythonSdkType
|
||||||
import com.jetbrains.python.sdk.add.target.createDetectedSdk
|
import com.jetbrains.python.sdk.add.target.createDetectedSdk
|
||||||
import com.jetbrains.python.ui.ManualPathEntryDialog
|
import com.jetbrains.python.ui.remotePathEditor.ManualPathEntryDialog
|
||||||
import java.awt.event.ActionListener
|
import java.awt.event.ActionListener
|
||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
import javax.swing.JComboBox
|
import javax.swing.JComboBox
|
||||||
@@ -94,7 +94,8 @@ class PySdkPathChoosingComboBox @JvmOverloads constructor(sdks: List<Sdk> = empt
|
|||||||
title,
|
title,
|
||||||
PY_SDK_COMBOBOX_TEXT_ACCESSOR,
|
PY_SDK_COMBOBOX_TEXT_ACCESSOR,
|
||||||
childComponent,
|
childComponent,
|
||||||
Supplier { targetEnvironmentConfiguration })
|
Supplier { targetEnvironmentConfiguration },
|
||||||
|
true)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// The fallback where the path is entered manually
|
// The fallback where the path is entered manually
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ fun TextFieldWithBrowseButton.withTargetBrowser(targetType: BrowsableTargetEnvir
|
|||||||
title,
|
title,
|
||||||
com.intellij.openapi.ui.TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT,
|
com.intellij.openapi.ui.TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT,
|
||||||
textField,
|
textField,
|
||||||
targetSupplier)
|
targetSupplier,
|
||||||
|
true)
|
||||||
addActionListener(browser)
|
addActionListener(browser)
|
||||||
}
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Copyright 2000-2021 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.ui
|
|
||||||
|
|
||||||
import com.intellij.execution.Platform
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.ui.DialogWrapper
|
|
||||||
import com.intellij.openapi.util.io.OSAgnosticPathUtil
|
|
||||||
import com.intellij.ui.layout.*
|
|
||||||
import com.jetbrains.python.PyBundle
|
|
||||||
import javax.swing.JComponent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The dialog that allows to specify the path to a file or directory manually.
|
|
||||||
*
|
|
||||||
* Performs validation that the path that is being added is an absolute path on
|
|
||||||
* the specified [platform].
|
|
||||||
*/
|
|
||||||
class ManualPathEntryDialog(project: Project?, private val platform: Platform) : DialogWrapper(project) {
|
|
||||||
var path: String = ""
|
|
||||||
private set
|
|
||||||
|
|
||||||
init {
|
|
||||||
title = PyBundle.message("enter.path.dialog.title")
|
|
||||||
init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createCenterPanel(): JComponent =
|
|
||||||
panel {
|
|
||||||
row(label = PyBundle.message("path.label")) {
|
|
||||||
textField(prop = ::path).withValidationOnApply { textField ->
|
|
||||||
val text = textField.text
|
|
||||||
when {
|
|
||||||
text.isBlank() -> error(PyBundle.message("path.must.not.be.empty.error.message"))
|
|
||||||
!isAbsolutePath(text, platform) -> error(PyBundle.message("path.must.be.absolute.error.message"))
|
|
||||||
text.endsWith(" ") -> warning(PyBundle.message("path.ends.with.whitespace.warning.message"))
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}.focused()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun isAbsolutePath(path: String, platform: Platform): Boolean = when (platform) {
|
|
||||||
Platform.UNIX -> path.startsWith("/")
|
|
||||||
Platform.WINDOWS -> isAbsoluteWindowsPath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isAbsoluteWindowsPath(path: String): Boolean = OSAgnosticPathUtil.isAbsoluteDosPath(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
package com.jetbrains.python.ui.remotePathEditor
|
||||||
|
|
||||||
|
import com.intellij.execution.Platform
|
||||||
|
import com.intellij.execution.target.BrowsableTargetEnvironmentType
|
||||||
|
import com.intellij.execution.target.TargetEnvironmentConfiguration
|
||||||
|
import com.intellij.execution.target.getTargetType
|
||||||
|
import com.intellij.execution.target.textFieldWithBrowseTargetButton
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.ui.DialogWrapper
|
||||||
|
import com.intellij.openapi.util.io.OSAgnosticPathUtil
|
||||||
|
import com.intellij.ui.layout.*
|
||||||
|
import com.jetbrains.python.PyBundle
|
||||||
|
import java.util.function.Supplier
|
||||||
|
import javax.swing.JComponent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dialog that allows to specify the path to a file or directory manually.
|
||||||
|
*
|
||||||
|
* Performs validation that the path that is being added is an absolute path on
|
||||||
|
* the specified [platform].
|
||||||
|
*/
|
||||||
|
class ManualPathEntryDialog(private val project: Project?,
|
||||||
|
private val platform: Platform = Platform.UNIX,
|
||||||
|
targetConfig: TargetEnvironmentConfiguration? = null) : DialogWrapper(project) {
|
||||||
|
|
||||||
|
private val targetConfigAndType: Pair<TargetEnvironmentConfiguration, BrowsableTargetEnvironmentType>? =
|
||||||
|
(targetConfig?.getTargetType() as? BrowsableTargetEnvironmentType)?.let { Pair(targetConfig, it) }
|
||||||
|
var path: String = ""
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
title = PyBundle.message("enter.path.dialog.title")
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createCenterPanel(): JComponent {
|
||||||
|
val label = PyBundle.message("path.label")
|
||||||
|
return panel {
|
||||||
|
row(label = label) {
|
||||||
|
val textFieldComponent = if (targetConfigAndType == null)
|
||||||
|
textField(prop = ::path)
|
||||||
|
else
|
||||||
|
textFieldWithBrowseTargetButton(this, targetConfigAndType.second, Supplier { targetConfigAndType.first }, project!!, label, this@ManualPathEntryDialog::path.toBinding(), false)
|
||||||
|
textFieldComponent.withValidationOnApply { textField ->
|
||||||
|
val text = textField.text
|
||||||
|
when {
|
||||||
|
text.isBlank() -> error(PyBundle.message("path.must.not.be.empty.error.message"))
|
||||||
|
!isAbsolutePath(text, platform) -> error(PyBundle.message("path.must.be.absolute.error.message"))
|
||||||
|
text.endsWith(" ") -> warning(PyBundle.message("path.ends.with.whitespace.warning.message"))
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}.focused()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun isAbsolutePath(path: String, platform: Platform): Boolean = when (platform) {
|
||||||
|
Platform.UNIX -> path.startsWith("/")
|
||||||
|
Platform.WINDOWS -> isAbsoluteWindowsPath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isAbsoluteWindowsPath(path: String): Boolean = OSAgnosticPathUtil.isAbsoluteDosPath(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
package com.jetbrains.python.ui
|
package com.jetbrains.python.ui
|
||||||
|
|
||||||
import com.intellij.execution.Platform
|
import com.intellij.execution.Platform
|
||||||
|
import com.jetbrains.python.ui.remotePathEditor.ManualPathEntryDialog
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|||||||
Reference in New Issue
Block a user