mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[python] (PY-84251) UI/UX changes for uv and Hatch dialogs
* adjust validated path field logic: remove 2 sec timer, remove the action icon, make a warning, add browse action + detect existing environment for uv selection based on the module path (show all existing virtual environments in the module root) GitOrigin-RevId: 0fb4cf1f77ebe799b4bc60f3a44974678e94bc5a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d7a31d1f12
commit
23fd6c469f
@@ -568,7 +568,7 @@ sdk.create.custom.venv.install.fix.title=Install {0} {1}
|
||||
sdk.create.custom.venv.run.error.message=Error Running {0}
|
||||
sdk.create.custom.venv.progress.title.detect.executable=Detect executable
|
||||
sdk.create.custom.existing.env.title=Environment:
|
||||
sdk.create.custom.existing.error.no.interpreters.to.select=No interpreters were found. Please select one manually using the Browse button.
|
||||
sdk.create.custom.existing.error.no.interpreters.to.select=No interpreters were found. Select manually using the Browse button.
|
||||
sdk.create.custom.hatch.environment=Environment:
|
||||
sdk.create.custom.hatch.environment.loading=Loading environments\u2026
|
||||
sdk.create.custom.hatch.environment.exists=Environment already exists
|
||||
@@ -1209,6 +1209,7 @@ python.add.sdk.panel.name.system.interpreter=System interpreter
|
||||
python.add.sdk.panel.name.virtualenv.environment=Virtualenv environment
|
||||
python.add.sdk.panel.name.poetry.environment=Poetry Environment
|
||||
python.add.sdk.wait.for.validation=Wait for the executable validation
|
||||
python.add.sdk.press.enter.to.validate=Press Enter to validate
|
||||
python.add.sdk.version=Version: {0}
|
||||
python.add.sdk.already.contains.python.with.version=Already contains Python with version {0}
|
||||
|
||||
|
||||
@@ -13,12 +13,7 @@ import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.provider.asNioPath
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.platform.eel.where
|
||||
import com.intellij.python.community.execService.Args
|
||||
import com.intellij.python.community.execService.BinOnEel
|
||||
import com.intellij.python.community.execService.BinOnTarget
|
||||
import com.intellij.python.community.execService.BinaryToExec
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.execGetStdout
|
||||
import com.intellij.python.community.execService.*
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
|
||||
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl
|
||||
import com.intellij.python.community.services.shared.VanillaPythonWithPythonInfo
|
||||
@@ -48,6 +43,7 @@ import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.isDirectory
|
||||
|
||||
|
||||
private val LOG: Logger = fileLogger()
|
||||
@@ -64,6 +60,7 @@ sealed interface FileSystem<P : PathHolder> {
|
||||
val isBrowseable: Boolean
|
||||
|
||||
fun parsePath(raw: String): PyResult<P>
|
||||
fun validateExecutable(path: P): PyResult<Unit>
|
||||
|
||||
suspend fun getSystemPythonFromSelection(pathToPython: P): PyResult<DetectedSelectableInterpreter<P>>
|
||||
|
||||
@@ -95,6 +92,14 @@ sealed interface FileSystem<P : PathHolder> {
|
||||
PyResult.localizedError(e.localizedMessage)
|
||||
}
|
||||
|
||||
override fun validateExecutable(path: PathHolder.Eel): PyResult<Unit> {
|
||||
return when {
|
||||
!path.path.exists() -> PyResult.localizedError(message("sdk.create.not.executable.does.not.exist.error"))
|
||||
path.path.isDirectory() -> PyResult.localizedError(message("sdk.create.executable.directory.error"))
|
||||
else -> PyResult.success(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun validateVenv(homePath: PathHolder.Eel): PyResult<Unit> = withContext(Dispatchers.IO) {
|
||||
val validationResult = when {
|
||||
!homePath.path.isAbsolute -> PyResult.localizedError(message("python.sdk.new.error.no.absolute"))
|
||||
@@ -202,6 +207,11 @@ sealed interface FileSystem<P : PathHolder> {
|
||||
return PyResult.success(PathHolder.Target(raw))
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently, we don't validate executable on target because there is no API to check path existence and its type on target.
|
||||
*/
|
||||
override fun validateExecutable(path: PathHolder.Target): PyResult<Unit> = PyResult.success(Unit)
|
||||
|
||||
override suspend fun validateVenv(homePath: PathHolder.Target): PyResult<Unit> = withContext(Dispatchers.IO) {
|
||||
val pythonBinaryPath = resolvePythonBinary(homePath)
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ class ToolValidator<P : PathHolder>(
|
||||
override val backProperty: ObservableMutableProperty<ValidatedPath.Executable<P>?>,
|
||||
propertyGraph: PropertyGraph,
|
||||
val defaultPathSupplier: suspend () -> P?,
|
||||
val pathValidator: suspend (P) -> PyResult<Version> = { fileSystem.getBinaryToExec(it).getToolVersion(toolVersionPrefix) },
|
||||
val toolValidator: suspend (P) -> PyResult<Version> = { fileSystem.getBinaryToExec(it).getToolVersion(toolVersionPrefix) },
|
||||
) : PathValidator<Version, P, ValidatedPath.Executable<P>> {
|
||||
override val isDirtyValue: ObservableMutableProperty<Boolean> = propertyGraph.property(true)
|
||||
override val isDirtyValue: ObservableMutableProperty<Boolean> = propertyGraph.property(false)
|
||||
override val isValidationInProgress: Boolean
|
||||
get() = validationJob.isActive
|
||||
|
||||
@@ -44,8 +44,13 @@ class ToolValidator<P : PathHolder>(
|
||||
val path = fileSystem.parsePath(input).getOr { error ->
|
||||
return@withContext ValidatedPath.Executable<P>(null, error)
|
||||
}
|
||||
val validationResult = pathValidator(path)
|
||||
ValidatedPath.Executable(path, validationResult)
|
||||
|
||||
fileSystem.validateExecutable(path).getOr {
|
||||
return@withContext ValidatedPath.Executable(path, it)
|
||||
}
|
||||
|
||||
val toolValidationResult = toolValidator(path)
|
||||
ValidatedPath.Executable(path, toolValidationResult)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.UI) { backProperty.set(exec) }
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.intellij.execution.target.getTargetType
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CustomShortcutSet
|
||||
import com.intellij.openapi.application.UI
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
@@ -36,30 +37,23 @@ import com.intellij.ui.dsl.builder.components.ValidationType
|
||||
import com.intellij.ui.dsl.builder.components.validationTooltip
|
||||
import com.intellij.util.asDisposable
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.onFailure
|
||||
import com.jetbrains.python.onSuccess
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.awt.AlphaComposite
|
||||
import java.awt.Component
|
||||
import java.awt.Graphics
|
||||
import java.awt.Graphics2D
|
||||
import java.awt.event.ActionListener
|
||||
import java.awt.event.KeyEvent
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import javax.swing.Icon
|
||||
import javax.swing.JTextField
|
||||
import javax.swing.KeyStroke
|
||||
import javax.swing.event.DocumentEvent
|
||||
import kotlin.concurrent.atomics.AtomicBoolean
|
||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||
import kotlin.math.cos
|
||||
|
||||
interface PathValidator<T, P : PathHolder, VP : ValidatedPath<T, P>> {
|
||||
val backProperty: ObservableMutableProperty<VP?>
|
||||
@@ -72,7 +66,9 @@ interface PathValidator<T, P : PathHolder, VP : ValidatedPath<T, P>> {
|
||||
}
|
||||
}
|
||||
|
||||
private class ValidationSuccessExtension<T>(val validationInfo: T) : ExtendableTextComponent.Extension {
|
||||
private interface ValidationStatusExtension
|
||||
|
||||
private class ValidationSuccessExtension<T>(val validationInfo: T) : ExtendableTextComponent.Extension, ValidationStatusExtension {
|
||||
override fun getIcon(hovered: Boolean): Icon = AllIcons.General.GreenCheckmark
|
||||
override fun getTooltip(): @NlsContexts.Tooltip String? {
|
||||
val tooltip = when (validationInfo) {
|
||||
@@ -83,68 +79,13 @@ private class ValidationSuccessExtension<T>(val validationInfo: T) : ExtendableT
|
||||
}
|
||||
}
|
||||
|
||||
private object ValidationErrorExtension : ExtendableTextComponent.Extension {
|
||||
override fun getIcon(hovered: Boolean): Icon = AllIcons.Status.FailedInProgress
|
||||
}
|
||||
|
||||
private object ValidationInProgressExtension : ExtendableTextComponent.Extension {
|
||||
private object ValidationInProgressExtension : ExtendableTextComponent.Extension, ValidationStatusExtension {
|
||||
override fun getIcon(hovered: Boolean): Icon = AnimatedIcon.Default()
|
||||
override fun getTooltip(): @NlsContexts.Tooltip String {
|
||||
return message("python.add.sdk.wait.for.validation")
|
||||
}
|
||||
}
|
||||
|
||||
private class DebounceCounterIcon(val icon: Icon, val period: Int) : Icon {
|
||||
private val time: AtomicLong = AtomicLong(System.currentTimeMillis())
|
||||
|
||||
fun reset() {
|
||||
time.set(System.currentTimeMillis())
|
||||
}
|
||||
|
||||
override fun getIconWidth(): Int {
|
||||
return icon.iconWidth
|
||||
}
|
||||
|
||||
override fun getIconHeight(): Int {
|
||||
return icon.iconHeight
|
||||
}
|
||||
|
||||
override fun paintIcon(c: Component?, g: Graphics?, x: Int, y: Int) {
|
||||
assert(period > 0) { "unexpected" }
|
||||
val time = (System.currentTimeMillis() - this.time.get()) % period
|
||||
val alpha = ((cos(2 * Math.PI * time / period) + 1) / 2).toFloat()
|
||||
if (alpha > 0) {
|
||||
if (alpha < 1 && g is Graphics2D) {
|
||||
val g2d = g.create() as Graphics2D
|
||||
try {
|
||||
g2d.composite = AlphaComposite.SrcAtop.derive(alpha)
|
||||
icon.paintIcon(c, g2d, x, y)
|
||||
}
|
||||
finally {
|
||||
g2d.dispose()
|
||||
}
|
||||
}
|
||||
else {
|
||||
icon.paintIcon(c, g, x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class AnimatedFadingIcon(private val icon: DebounceCounterIcon) : AnimatedIcon(50, icon) {
|
||||
fun reset() {
|
||||
icon.reset()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun build(icon: Icon, period: Int = VALIDATION_DELAY * 2): AnimatedFadingIcon {
|
||||
return AnimatedFadingIcon(DebounceCounterIcon(icon, period))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val VALIDATION_DELAY = 2000
|
||||
|
||||
@OptIn(FlowPreview::class, ExperimentalAtomicApi::class)
|
||||
internal class ValidatedPathField<T, P : PathHolder, VP : ValidatedPath<T, P>>(
|
||||
val fileSystem: FileSystem<P>,
|
||||
@@ -170,23 +111,12 @@ internal class ValidatedPathField<T, P : PathHolder, VP : ValidatedPath<T, P>>(
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
with(textField as ExtendableTextComponent) {
|
||||
if (extensions.contains(validationWaitExtension)) {
|
||||
doValidate()
|
||||
}
|
||||
}
|
||||
doValidate()
|
||||
}
|
||||
}.apply {
|
||||
registerCustomShortcutSet(CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)), this@ValidatedPathField)
|
||||
}
|
||||
|
||||
private val validationWaitIcon = AnimatedFadingIcon.build(AllIcons.Gutter.SuggestedRefactoringBulb)
|
||||
private val validationWaitExtension: ExtendableTextComponent.Extension = ExtendableTextComponent.Extension.create(
|
||||
validationWaitIcon, message("python.add.sdk.wait.for.validation")
|
||||
) {
|
||||
validationAction.doValidate()
|
||||
}
|
||||
|
||||
private val fieldAccessor = object : TextComponentAccessor<JTextField> {
|
||||
override fun getText(component: JTextField): @NlsSafe String {
|
||||
return component.text
|
||||
@@ -227,27 +157,22 @@ internal class ValidatedPathField<T, P : PathHolder, VP : ValidatedPath<T, P>>(
|
||||
|
||||
pathValidator.isDirtyValue.afterChange(scope.asDisposable()) { isDirtyValue ->
|
||||
with(textField as ExtendableTextComponent) {
|
||||
extensions.forEach { removeExtension(it) }
|
||||
extensions
|
||||
.filter { it is ValidationStatusExtension }
|
||||
.forEach { removeExtension(it) }
|
||||
|
||||
if (isDirtyValue) {
|
||||
if (pathValidator.isValidationInProgress) {
|
||||
isEnabled = false
|
||||
addExtension(ValidationInProgressExtension)
|
||||
}
|
||||
else {
|
||||
addExtension(validationWaitExtension)
|
||||
}
|
||||
}
|
||||
else {
|
||||
editorMode.store(false)
|
||||
browseFolderActionLister?.let { setButtonVisible(true) }
|
||||
isEnabled = true
|
||||
|
||||
pathValidator.backProperty.get()?.validationResult?.let { validationResult ->
|
||||
validationResult
|
||||
.onFailure {
|
||||
addExtension(ValidationErrorExtension)
|
||||
}
|
||||
.onSuccess {
|
||||
addExtension(ValidationSuccessExtension(it))
|
||||
}
|
||||
@@ -261,27 +186,20 @@ internal class ValidatedPathField<T, P : PathHolder, VP : ValidatedPath<T, P>>(
|
||||
this.scope = scope
|
||||
registerPropertyCallbacks()
|
||||
|
||||
scope.launch {
|
||||
scope.launch(Dispatchers.UI) {
|
||||
textInputFlow
|
||||
.debounce(50) // setText method is a combination of two calls - remove + insert, should count them as 1
|
||||
.map {
|
||||
if (it == null) return@map null
|
||||
|
||||
if (editorMode.load()) {
|
||||
validationWaitIcon.reset()
|
||||
}
|
||||
else if ((pathValidator.backProperty.get()?.pathHolder?.toString() ?: "") != it) {
|
||||
if (!editorMode.load() && (pathValidator.backProperty.get()?.pathHolder?.toString() ?: "") != it) {
|
||||
editorMode.store(true)
|
||||
pathValidator.markDirty()
|
||||
}
|
||||
|
||||
it
|
||||
}
|
||||
.debounce(VALIDATION_DELAY.toLong())
|
||||
.collectLatest {
|
||||
if (it == null) return@collectLatest
|
||||
validationAction.doValidate()
|
||||
}
|
||||
.collect {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,9 +317,12 @@ internal fun <T, P : PathHolder, VP : ValidatedPath<T, P>> Panel.validatablePath
|
||||
pyErrorMessage != null -> {
|
||||
ValidationInfo(pyErrorMessage)
|
||||
}
|
||||
pathValidator.isDirtyValue.get() -> {
|
||||
pathValidator.isValidationInProgress -> {
|
||||
ValidationInfo(message("python.add.sdk.wait.for.validation"))
|
||||
}
|
||||
pathValidator.isDirtyValue.get() -> {
|
||||
ValidationInfo(message("python.add.sdk.press.enter.to.validate")).asWarning()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.intellij.ui.dsl.builder.bindItem
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||
import com.jetbrains.python.sdk.ModuleOrProject
|
||||
@@ -130,10 +129,6 @@ internal class CondaExistingEnvironmentSelector<P : PathHolder>(model: PythonAdd
|
||||
scope = scope,
|
||||
)
|
||||
condaExecutable.initialize(scope)
|
||||
condaExecutable.displayLoaderWhen(
|
||||
loading = model.condaViewModel.condaEnvironmentsLoading,
|
||||
scope = scope,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): PyResult<Sdk> {
|
||||
|
||||
@@ -10,7 +10,6 @@ import com.intellij.ui.dsl.builder.bindItem
|
||||
import com.intellij.ui.dsl.builder.bindText
|
||||
import com.intellij.ui.dsl.listCellRenderer.textListCellRenderer
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
@@ -56,10 +55,6 @@ internal class CondaNewEnvironmentCreator<P: PathHolder>(model: PythonMutableTar
|
||||
|
||||
override fun onShown(scope: CoroutineScope) {
|
||||
condaExecutable.initialize(scope)
|
||||
condaExecutable.displayLoaderWhen(
|
||||
loading = model.condaViewModel.condaEnvironmentsLoading,
|
||||
scope = scope,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): PyResult<Sdk> {
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
|
||||
import com.intellij.openapi.observable.util.transform
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.FixedComboBoxEditor
|
||||
import com.intellij.openapi.ui.ValidationInfo
|
||||
import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
||||
import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
|
||||
@@ -73,12 +74,10 @@ private class HatchEnvComboBoxListCellRenderer(val contentFlow: StateFlow<PyResu
|
||||
append(message("sdk.create.custom.hatch.environment.loading"), SimpleTextAttributes.GRAYED_ITALIC_ATTRIBUTES)
|
||||
}
|
||||
is Result.Failure -> {
|
||||
icon = AllIcons.General.ShowWarning
|
||||
append(message("sdk.create.custom.hatch.error.no.environments.to.select"), SimpleTextAttributes.GRAYED_ITALIC_ATTRIBUTES)
|
||||
}
|
||||
is Result.Success -> {
|
||||
if (result.result.isEmpty() || value == null) {
|
||||
icon = AllIcons.General.ShowWarning
|
||||
append(message("sdk.create.custom.hatch.error.no.environments.to.select"), SimpleTextAttributes.GRAYED_ITALIC_ATTRIBUTES)
|
||||
}
|
||||
else {
|
||||
@@ -98,14 +97,36 @@ private class HatchEnvComboBoxListCellRenderer(val contentFlow: StateFlow<PyResu
|
||||
}
|
||||
}
|
||||
|
||||
internal class HatchEnvironmentComboBox<P : PathHolder>(
|
||||
model: PythonAddInterpreterModel<P>,
|
||||
) : ComboBox<HatchVirtualEnvironment?>() {
|
||||
init {
|
||||
renderer = HatchEnvComboBoxListCellRenderer(model.hatchViewModel.availableEnvironments)
|
||||
editor = FixedComboBoxEditor()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
internal fun syncWithEnvs(
|
||||
environmentsResult: PyResult<List<HatchVirtualEnvironment>>,
|
||||
isFilterOnlyExisting: Boolean = false,
|
||||
) {
|
||||
removeAllItems()
|
||||
val environments = environmentsResult.getOr { return }
|
||||
environments.filter { !isFilterOnlyExisting || it.pythonVirtualEnvironment is PythonVirtualEnvironment.Existing }.forEach {
|
||||
addItem(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun <P : PathHolder> Panel.addEnvironmentComboBox(
|
||||
model: PythonAddInterpreterModel<P>,
|
||||
validationRequestor: DialogValidationRequestor,
|
||||
isGenerateNewMode: Boolean,
|
||||
): ComboBox<HatchVirtualEnvironment> {
|
||||
): HatchEnvironmentComboBox<P> {
|
||||
val environmentAlreadyExists = AtomicBooleanProperty(false)
|
||||
|
||||
lateinit var environmentComboBox: ComboBox<HatchVirtualEnvironment>
|
||||
val environmentComboBox = HatchEnvironmentComboBox(model)
|
||||
|
||||
val hatchEnvironmentProperty = if (isGenerateNewMode)
|
||||
model.hatchViewModel.selectedEnvFromAvailable
|
||||
@@ -113,7 +134,7 @@ private fun <P : PathHolder> Panel.addEnvironmentComboBox(
|
||||
model.hatchViewModel.selectedEnvFromExisting
|
||||
|
||||
row(message("sdk.create.custom.hatch.environment")) {
|
||||
environmentComboBox = comboBox(emptyList(), HatchEnvComboBoxListCellRenderer(model.hatchViewModel.availableEnvironments))
|
||||
cell(environmentComboBox)
|
||||
.bindItem(hatchEnvironmentProperty)
|
||||
.validationRequestor(validationRequestor and WHEN_PROPERTY_CHANGED(hatchEnvironmentProperty))
|
||||
.validationInfo { component ->
|
||||
@@ -121,7 +142,7 @@ private fun <P : PathHolder> Panel.addEnvironmentComboBox(
|
||||
when {
|
||||
!component.isVisible || !component.isEnabled -> null
|
||||
component.item == null -> {
|
||||
ValidationInfo(message("sdk.create.custom.hatch.error.no.environments.to.select"))
|
||||
ValidationInfo("")
|
||||
}
|
||||
isGenerateNewMode && component.item?.pythonVirtualEnvironment is PythonVirtualEnvironment.Existing -> {
|
||||
environmentAlreadyExists.set(true)
|
||||
@@ -134,7 +155,6 @@ private fun <P : PathHolder> Panel.addEnvironmentComboBox(
|
||||
.applyToComponent {
|
||||
preferredSize = JBUI.size(preferredSize)
|
||||
}
|
||||
.component
|
||||
}
|
||||
|
||||
row("") {
|
||||
@@ -171,7 +191,7 @@ private fun <P : PathHolder> Panel.addExecutableSelector(
|
||||
}
|
||||
|
||||
internal data class HatchFormFields<P : PathHolder>(
|
||||
val environmentComboBox: ComboBox<HatchVirtualEnvironment>,
|
||||
val environmentComboBox: HatchEnvironmentComboBox<P>,
|
||||
val basePythonComboBox: PythonInterpreterComboBox<P>?,
|
||||
val validatedPathField: ValidatedPathField<Version, P, ValidatedPath.Executable<P>>,
|
||||
) {
|
||||
@@ -213,7 +233,7 @@ internal fun <P : PathHolder> Panel.buildHatchFormFields(
|
||||
validationRequestor,
|
||||
installHatchActionLink
|
||||
)
|
||||
var environmentComboBox: ComboBox<HatchVirtualEnvironment> by lateinitVal()
|
||||
var environmentComboBox: HatchEnvironmentComboBox<P> by lateinitVal()
|
||||
var basePythonComboBox: PythonInterpreterComboBox<P>? = null
|
||||
|
||||
rowsRange {
|
||||
@@ -238,15 +258,3 @@ internal fun <P : PathHolder> Panel.buildHatchFormFields(
|
||||
return HatchFormFields(environmentComboBox, basePythonComboBox, executablePath)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
internal fun ComboBox<HatchVirtualEnvironment>.syncWithEnvs(
|
||||
environmentsResult: PyResult<List<HatchVirtualEnvironment>>,
|
||||
isFilterOnlyExisting: Boolean = false,
|
||||
) {
|
||||
removeAllItems()
|
||||
val environments = environmentsResult.getOr { return }
|
||||
environments.filter { !isFilterOnlyExisting || it.pythonVirtualEnvironment is PythonVirtualEnvironment.Existing }.forEach {
|
||||
addItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ class HatchViewModel<P : PathHolder>(
|
||||
|
||||
hatchExecutable.afterChange { hatchExecutable ->
|
||||
if (hatchExecutable?.validationResult?.successOrNull == null) {
|
||||
availableEnvironments.value = null
|
||||
availableEnvironments.value = PyResult.success(emptyList())
|
||||
return@afterChange
|
||||
}
|
||||
|
||||
|
||||
@@ -178,7 +178,6 @@ internal fun <P : PathHolder> SimpleColoredComponent.customizeForPythonInterpret
|
||||
return
|
||||
}
|
||||
interpreter == null -> {
|
||||
icon = AllIcons.General.ShowWarning
|
||||
append(message("sdk.create.custom.existing.error.no.interpreters.to.select"), SimpleTextAttributes.GRAYED_ITALIC_ATTRIBUTES)
|
||||
return
|
||||
}
|
||||
@@ -304,7 +303,7 @@ internal fun <P : PathHolder> Panel.pythonInterpreterComboBox(
|
||||
when {
|
||||
!it.isVisible -> null
|
||||
it.isLoading.get() -> ValidationInfo(message("python.add.sdk.panel.wait"))
|
||||
selectedSdkProperty.get() == null -> ValidationInfo(message("sdk.create.custom.existing.error.no.interpreters.to.select"))
|
||||
selectedSdkProperty.get() == null -> ValidationInfo("")
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,30 @@ package com.jetbrains.python.sdk.add.v2.uv
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PythonInfo
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.getOrNull
|
||||
import com.jetbrains.python.sdk.ModuleOrProject
|
||||
import com.jetbrains.python.sdk.add.v2.*
|
||||
import com.jetbrains.python.sdk.associatedModulePath
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.impl.resolvePythonBinary
|
||||
import com.jetbrains.python.sdk.isAssociatedWithModule
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.uv.isUv
|
||||
import com.jetbrains.python.sdk.uv.setupExistingEnvAndSdk
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import com.jetbrains.python.statistics.version
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import com.jetbrains.python.venvReader.tryResolvePath
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.stream.Collectors
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
|
||||
internal class UvExistingEnvironmentSelector<P : PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>, module: Module?)
|
||||
: CustomExistingEnvironmentSelector<P>("uv", model, module) {
|
||||
override val toolState: PathValidator<Version, P, ValidatedPath.Executable<P>> = model.uvViewModel.toolValidator
|
||||
@@ -58,14 +63,18 @@ internal class UvExistingEnvironmentSelector<P : PathHolder>(model: PythonMutabl
|
||||
}
|
||||
|
||||
override suspend fun detectEnvironments(modulePath: Path): List<DetectedSelectableInterpreter<P>> {
|
||||
val existingEnvs = PythonSdkUtil.getAllSdks().filter {
|
||||
it.isUv && (it.associatedModulePath == modulePath.pathString || it.associatedModulePath == null)
|
||||
}.mapNotNull { env ->
|
||||
env.homePath?.let { path ->
|
||||
model.fileSystem.parsePath(path).successOrNull?.let { homePath ->
|
||||
DetectedSelectableInterpreter(homePath, PythonInfo(env.version), false)
|
||||
}
|
||||
}
|
||||
val rootFolders = Files.walk(modulePath, 1)
|
||||
.filter(Files::isDirectory)
|
||||
.collect(Collectors.toList())
|
||||
|
||||
val existingEnvs = rootFolders.mapNotNull { possibleVenvHome ->
|
||||
val pythonBinaryPath = possibleVenvHome.resolvePythonBinary()?.takeIf { it.exists() }
|
||||
?: return@mapNotNull null
|
||||
val pythonInfo = pythonBinaryPath.validatePythonAndGetInfo().getOrNull()
|
||||
?: return@mapNotNull null
|
||||
|
||||
val pathHolder = PathHolder.Eel(pythonBinaryPath) as P
|
||||
DetectedSelectableInterpreter(pathHolder, pythonInfo, false)
|
||||
}
|
||||
return existingEnvs
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user