PY-85637 Persist tool path when selecting existing environment

GitOrigin-RevId: 9e10ee0c38a025286f53362cf834bfcce57b665f
This commit is contained in:
Alexey Katsman
2025-11-14 15:33:29 +01:00
committed by intellij-monorepo-bot
parent 79d33d4b01
commit cdfdd3e82f
16 changed files with 106 additions and 91 deletions

View File

@@ -4,7 +4,6 @@ package com.jetbrains.python.sdk.add.v2
import com.intellij.openapi.application.EDT
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.observable.util.transform
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.ui.dsl.builder.Panel
import com.jetbrains.python.PyBundle.message
@@ -19,8 +18,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus.Internal
import java.nio.file.Path
import java.util.*
@Internal
internal abstract class CustomExistingEnvironmentSelector<P : PathHolder>(
@@ -52,7 +49,7 @@ internal abstract class CustomExistingEnvironmentSelector<P : PathHolder>(
validationRequestor = validationRequestor,
onPathSelected = model::addManuallyAddedInterpreter,
) {
visibleIf(toolState.backProperty.transform { it?.validationResult?.successOrNull != null })
visibleIf(toolState.isValidationSuccessful)
}
}
}
@@ -90,12 +87,6 @@ internal abstract class CustomExistingEnvironmentSelector<P : PathHolder>(
)
}
//private fun addEnvByPath(python: VanillaPythonWithLanguageLevel): PythonSelectableInterpreter {
// val interpreter = ManuallyAddedSelectableInterpreter(python)
// existingEnvironments.value = (existingEnvironments.value ?: emptyList()) + interpreter
// return interpreter
//}
internal abstract val toolState: PathValidator<Version, P, ValidatedPath.Executable<P>>
internal abstract val interpreterType: InterpreterType
internal abstract suspend fun detectEnvironments(modulePath: Path): List<DetectedSelectableInterpreter<P>>

View File

@@ -71,8 +71,6 @@ internal abstract class CustomNewEnvironmentCreator<P : PathHolder>(
}
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): PyResult<Sdk> {
savePathToExecutableToProperties(null)
// todo think about better error handling
val selectedBasePython = model.state.baseInterpreter.get()!!
val basePythonBinaryPath = model.installPythonIfNeeded(selectedBasePython)
@@ -169,7 +167,7 @@ internal abstract class CustomNewEnvironmentCreator<P : PathHolder>(
when (val r = installExecutableViaPythonScript(pythonExecutable, "-n", name, *versionArgs.toTypedArray())) {
is Result.Success -> {
val pathHolder = PathHolder.Eel(r.result)
savePathToExecutableToProperties(pathHolder)
savePathToExecutableToProperties(pathHolder as? P)
}
is Result.Failure -> {
errorSink.emit(r.error)
@@ -184,14 +182,6 @@ internal abstract class CustomNewEnvironmentCreator<P : PathHolder>(
internal open val installationVersion: String? = null
/**
* Saves the provided path to an executable in the properties of the environment
*
* @param [path] The path to the executable that needs to be saved. This may be null when tries to find automatically.
*/
internal abstract suspend fun savePathToExecutableToProperties(pathHolder: PathHolder?)
protected abstract suspend fun setupEnvSdk(moduleBasePath: Path, baseSdks: List<Sdk>, basePythonBinaryPath: P?, installPackages: Boolean): PyResult<Sdk>
internal open fun onVenvSelectExisting() {}

View File

@@ -11,6 +11,7 @@ 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
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.observable.util.and
import com.intellij.openapi.observable.util.not
import com.intellij.openapi.observable.util.transform
@@ -56,6 +57,9 @@ import kotlin.concurrent.atomics.AtomicBoolean
import kotlin.concurrent.atomics.ExperimentalAtomicApi
interface PathValidator<T, P : PathHolder, VP : ValidatedPath<T, P>> {
/**
* [backProperty] should only be used in [PathValidator] and its inheritors
*/
val backProperty: ObservableMutableProperty<VP?>
val isDirtyValue: ObservableMutableProperty<Boolean>
val isValidationInProgress: Boolean
@@ -64,6 +68,9 @@ interface PathValidator<T, P : PathHolder, VP : ValidatedPath<T, P>> {
isDirtyValue.set(true)
backProperty.set(null)
}
val isValidationSuccessful: ObservableProperty<Boolean>
get() = backProperty.transform { it?.validationResult?.successOrNull != null }
}
private interface ValidationStatusExtension

View File

@@ -11,6 +11,7 @@ import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.help.HelpManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.AtomicProperty
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
@@ -63,6 +64,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Path
import javax.swing.Icon
abstract class PythonAddEnvironment<P : PathHolder>(open val model: PythonAddInterpreterModel<P>) {
@@ -73,6 +75,9 @@ abstract class PythonAddEnvironment<P : PathHolder>(open val model: PythonAddInt
internal val propertyGraph
get() = model.propertyGraph
protected abstract val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?>?
protected abstract val toolExecutablePersister: suspend (P) -> Unit
abstract fun setupUI(panel: Panel, validationRequestor: DialogValidationRequestor)
abstract fun onShown(scope: CoroutineScope)
@@ -84,6 +89,7 @@ abstract class PythonAddEnvironment<P : PathHolder>(open val model: PythonAddInt
protected abstract suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): PyResult<Sdk>
protected suspend fun setupSdk(moduleOrProject: ModuleOrProject): PyResult<Sdk> {
savePathToExecutableToProperties(null)
val sdk = getOrCreateSdk(moduleOrProject).getOr { return it }
moduleOrProject.project.excludeInnerVirtualEnv(sdk)
@@ -110,6 +116,17 @@ abstract class PythonAddEnvironment<P : PathHolder>(open val model: PythonAddInt
}
}
/**
* Saves the provided path to an executable in the properties of the environment
*
* @param [pathHolder] The path holder of the path to the executable that needs to be saved. This may be null when we try to find the tool automatically.
*/
protected suspend fun savePathToExecutableToProperties(pathHolder: P?) {
val savingPath = pathHolder ?: toolExecutable?.get()?.pathHolder ?: return
if (!model.fileSystem.isLocal) return
toolExecutablePersister(savingPath)
}
open suspend fun createPythonModuleStructure(module: Module): PyResult<Unit> = Result.success(Unit)
abstract fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo
@@ -316,3 +333,10 @@ internal suspend fun BinaryToExec.getToolVersion(toolVersionPrefix: String): PyR
PyResult.localizedError(message("selected.tool.is.wrong", toolVersionPrefix.trim(), versionPresentation))
}
}
internal fun savePathForEelOnly(pathHolder: PathHolder, pathPersister: (Path) -> Unit) {
when (pathHolder) {
is PathHolder.Eel -> pathPersister(pathHolder.path)
is PathHolder.Target -> Unit
}
}

View File

@@ -3,6 +3,7 @@ package com.jetbrains.python.sdk.add.v2.conda
import com.intellij.openapi.application.EDT
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.observable.util.transform
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.ComboBox
@@ -18,6 +19,7 @@ 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.conda.saveLocalPythonCondaPath
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.sdk.ModuleOrProject
@@ -42,7 +44,10 @@ internal class CondaExistingEnvironmentSelector<P : PathHolder>(model: PythonAdd
private lateinit var condaExecutable: ValidatedPathField<Version, P, ValidatedPath.Executable<P>>
private lateinit var reloadLink: ActionLink
private val isReloadLinkVisible = AtomicBooleanProperty(false)
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?> = model.condaViewModel.condaExecutable
override val toolExecutablePersister: suspend (P) -> Unit = { pathHolder ->
savePathForEelOnly(pathHolder) { path -> saveLocalPythonCondaPath(path) }
}
override fun setupUI(panel: Panel, validationRequestor: DialogValidationRequestor) {
with(panel) {

View File

@@ -2,6 +2,7 @@
package com.jetbrains.python.sdk.add.v2.conda
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.validation.DialogValidationRequestor
@@ -10,6 +11,7 @@ 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.conda.saveLocalPythonCondaPath
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.psi.LanguageLevel
@@ -22,11 +24,15 @@ import com.jetbrains.python.statistics.InterpreterType
import com.jetbrains.python.ui.flow.bindText
import kotlinx.coroutines.CoroutineScope
internal class CondaNewEnvironmentCreator<P: PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>) : PythonNewEnvironmentCreator<P>(model) {
internal class CondaNewEnvironmentCreator<P : PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>) : PythonNewEnvironmentCreator<P>(model) {
private lateinit var pythonVersion: ObservableMutableProperty<LanguageLevel>
private lateinit var versionComboBox: ComboBox<LanguageLevel>
private lateinit var condaExecutable: ValidatedPathField<Version, P, ValidatedPath.Executable<P>>
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?> = model.condaViewModel.condaExecutable
override val toolExecutablePersister: suspend (P) -> Unit = { pathHolder ->
savePathForEelOnly(pathHolder) { path -> saveLocalPythonCondaPath(path) }
}
override fun setupUI(panel: Panel, validationRequestor: DialogValidationRequestor) {
with(panel) {

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.add.v2.hatch
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.python.hatch.HatchConfiguration
@@ -12,11 +13,11 @@ import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.hatch.sdk.createSdk
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.onSuccess
import com.jetbrains.python.sdk.impl.resolvePythonBinary
import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.add.v2.*
import com.jetbrains.python.sdk.destructured
import com.jetbrains.python.sdk.impl.resolvePythonBinary
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.sdk.setAssociationToModule
import com.jetbrains.python.statistics.InterpreterCreationMode
import com.jetbrains.python.statistics.InterpreterType
@@ -24,12 +25,16 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal class HatchExistingEnvironmentSelector<P: PathHolder>(
internal class HatchExistingEnvironmentSelector<P : PathHolder>(
override val model: PythonMutableTargetAddInterpreterModel<P>,
) : PythonExistingEnvironmentConfigurator<P>(model) {
val interpreterType: InterpreterType = InterpreterType.HATCH
private lateinit var hatchFormFields: HatchFormFields<P>
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?> = model.hatchViewModel.hatchExecutable
override val toolExecutablePersister: suspend (P) -> Unit = { pathHolder ->
savePathForEelOnly(pathHolder) { path -> HatchConfiguration.persistPathForTarget(hatchExecutablePath = path) }
}
override fun setupUI(panel: Panel, validationRequestor: DialogValidationRequestor) {
hatchFormFields = panel.buildHatchFormFields(

View File

@@ -2,6 +2,7 @@
package com.jetbrains.python.sdk.add.v2.hatch
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.ui.validation.DialogValidationRequestor
@@ -28,6 +29,10 @@ internal class HatchNewEnvironmentCreator<P : PathHolder>(
override val interpreterType: InterpreterType = InterpreterType.HATCH
override val toolValidator: ToolValidator<P> = model.hatchViewModel.toolValidator
private lateinit var hatchFormFields: HatchFormFields<P>
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?> = model.hatchViewModel.hatchExecutable
override val toolExecutablePersister: suspend (P) -> Unit = { pathHolder ->
savePathForEelOnly(pathHolder) { path -> HatchConfiguration.persistPathForTarget(hatchExecutablePath = path) }
}
override fun setupUI(panel: Panel, validationRequestor: DialogValidationRequestor) {
hatchFormFields = panel.buildHatchFormFields(
@@ -45,14 +50,8 @@ internal class HatchNewEnvironmentCreator<P : PathHolder>(
hatchFormFields.onShown(scope, model, isFilterOnlyExisting = false)
}
override suspend fun savePathToExecutableToProperties(pathHolder: PathHolder?) {
val savingPath = pathHolder ?: toolValidator.backProperty.get()?.pathHolder ?: return
val eelPath = (savingPath as? PathHolder.Eel)?.path ?: return
HatchConfiguration.persistPathForTarget(hatchExecutablePath = eelPath)
}
override suspend fun createPythonModuleStructure(module: Module): PyResult<Unit> {
val hatchExecutablePath = (toolValidator.backProperty.get()?.pathHolder as? PathHolder.Eel)?.path
val hatchExecutablePath = (model.hatchViewModel.hatchExecutable.get()?.pathHolder as? PathHolder.Eel)?.path
?: return Result.failure(HatchUIError.HatchExecutablePathIsNotValid(null))
val hatchService = module.getHatchService(hatchExecutablePath).getOr { return it }
@@ -78,7 +77,7 @@ internal class HatchNewEnvironmentCreator<P : PathHolder>(
is PathHolder.Eel -> basePythonBinaryPath.path
else -> return PyResult.localizedError(PyBundle.message("target.is.not.supported", basePythonBinaryPath))
}
val hatchExecutablePath = when (val hatchBinary = toolValidator.backProperty.get()?.pathHolder) {
val hatchExecutablePath = when (val hatchBinary = model.hatchViewModel.hatchExecutable.get()?.pathHolder) {
is PathHolder.Eel -> hatchBinary.path
else -> null
}

View File

@@ -212,7 +212,7 @@ internal data class HatchFormFields<P : PathHolder>(
with(validatedPathField) {
initialize(scope)
pathValidator.backProperty.afterChange { executable ->
model.hatchViewModel.hatchExecutable.afterChange { executable ->
if (executable != model.hatchViewModel.hatchExecutable.get()) {
model.hatchViewModel.hatchExecutable.set(executable)
}

View File

@@ -2,17 +2,13 @@
package com.jetbrains.python.sdk.add.v2.pipenv
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.platform.eel.LocalEelApi
import com.intellij.python.community.impl.pipenv.pipenvPath
import com.jetbrains.python.PyBundle
import com.jetbrains.python.errorProcessing.ErrorSink
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.sdk.add.v2.CustomNewEnvironmentCreator
import com.jetbrains.python.sdk.add.v2.FileSystem
import com.jetbrains.python.sdk.add.v2.PathHolder
import com.jetbrains.python.sdk.add.v2.PythonMutableTargetAddInterpreterModel
import com.jetbrains.python.sdk.add.v2.ToolValidator
import com.jetbrains.python.sdk.add.v2.*
import com.jetbrains.python.sdk.pipenv.setupPipEnvSdkWithProgressReport
import com.jetbrains.python.statistics.InterpreterType
import java.nio.file.Path
@@ -20,15 +16,9 @@ import java.nio.file.Path
internal class EnvironmentCreatorPip<P : PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>, errorSink: ErrorSink) : CustomNewEnvironmentCreator<P>("pipenv", model, errorSink) {
override val interpreterType: InterpreterType = InterpreterType.PIPENV
override val toolValidator: ToolValidator<P> = model.pipenvViewModel.toolValidator
override suspend fun savePathToExecutableToProperties(pathHolder: PathHolder?) {
if ((model.fileSystem as? FileSystem.Eel)?.eelApi !is LocalEelApi) return
val savingPath = (pathHolder as? PathHolder.Eel)?.path
?: (toolValidator.backProperty.get()?.pathHolder as? PathHolder.Eel)?.path
savingPath?.let {
PropertiesComponent.getInstance().pipenvPath = it.toString()
}
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?> = model.pipenvViewModel.pipenvExecutable
override val toolExecutablePersister: suspend (P) -> Unit = { pathHolder ->
savePathForEelOnly(pathHolder) { path -> PropertiesComponent.getInstance().pipenvPath = path.toString() }
}
override suspend fun setupEnvSdk(moduleBasePath: Path, baseSdks: List<Sdk>, basePythonBinaryPath: P?, installPackages: Boolean): PyResult<Sdk> {

View File

@@ -5,10 +5,10 @@ import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.*
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.platform.eel.LocalEelApi
import com.intellij.python.community.impl.poetry.common.poetryPath
import com.intellij.python.pyproject.PyProjectToml
import com.intellij.ui.dsl.builder.Panel
@@ -39,7 +39,7 @@ import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.exists
internal class EnvironmentCreatorPoetry<P: PathHolder>(
internal class EnvironmentCreatorPoetry<P : PathHolder>(
model: PythonMutableTargetAddInterpreterModel<P>,
private val module: Module?,
errorSink: ErrorSink,
@@ -47,6 +47,10 @@ internal class EnvironmentCreatorPoetry<P: PathHolder>(
override val interpreterType: InterpreterType = InterpreterType.POETRY
override val toolValidator: ToolValidator<P> = model.poetryViewModel.toolValidator
override val installationVersion: String = "1.8.0"
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?> = model.poetryViewModel.poetryExecutable
override val toolExecutablePersister: suspend (P) -> Unit = { pathHolder ->
savePathForEelOnly(pathHolder) { path -> PropertiesComponent.getInstance().poetryPath = path.toString() }
}
private val isInProjectEnvFlow = MutableStateFlow(service<PoetryConfigService>().state.isInProjectEnv)
private val isInProjectEnvProp = propertyGraph.property(isInProjectEnvFlow.value)
@@ -99,21 +103,10 @@ internal class EnvironmentCreatorPoetry<P: PathHolder>(
}
}
override suspend fun savePathToExecutableToProperties(pathHolder: PathHolder?) {
if ((model.fileSystem as? FileSystem.Eel)?.eelApi !is LocalEelApi) return
val savingPath = (pathHolder as? PathHolder.Eel)?.path
?: (toolValidator.backProperty.get()?.pathHolder as? PathHolder.Eel)?.path
savingPath?.let {
PropertiesComponent.getInstance().poetryPath = it.toString()
}
}
override suspend fun setupEnvSdk(moduleBasePath: Path, baseSdks: List<Sdk>, basePythonBinaryPath: P?, installPackages: Boolean): PyResult<Sdk> {
module?.let { service<PoetryConfigService>().setInProjectEnv(it) }
return when (basePythonBinaryPath) {
is PathHolder.Eel -> createNewPoetrySdk(moduleBasePath, baseSdks, basePythonBinaryPath.path, installPackages)
is PathHolder.Eel -> createNewPoetrySdk(moduleBasePath, baseSdks, basePythonBinaryPath.path, installPackages)
else -> return PyResult.localizedError(PyBundle.message("target.is.not.supported", basePythonBinaryPath))
}
}

View File

@@ -1,9 +1,12 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.add.v2.poetry
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.python.community.impl.poetry.common.poetryPath
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonInfo
import com.jetbrains.python.Result
@@ -22,8 +25,12 @@ import java.nio.file.Path
import kotlin.io.path.pathString
internal class PoetryExistingEnvironmentSelector<P : PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>, module: Module?) : CustomExistingEnvironmentSelector<P>("poetry", model, module) {
override val toolState: PathValidator<Version, P, ValidatedPath.Executable<P>> = model.poetryViewModel.toolValidator
override val interpreterType: InterpreterType = InterpreterType.POETRY
override val toolState: ToolValidator<P> = model.poetryViewModel.toolValidator
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?> = model.poetryViewModel.poetryExecutable
override val toolExecutablePersister: suspend (P) -> Unit = { pathHolder ->
savePathForEelOnly(pathHolder) { path -> PropertiesComponent.getInstance().poetryPath = path.toString() }
}
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): PyResult<Sdk> {

View File

@@ -4,11 +4,11 @@ package com.jetbrains.python.sdk.add.v2.uv
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.observable.util.not
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.platform.eel.LocalEelApi
import com.intellij.python.pyproject.PY_PROJECT_TOML
import com.intellij.python.pyproject.PyProjectToml
import com.intellij.ui.dsl.builder.AlignX
@@ -66,11 +66,15 @@ internal class EnvironmentCreatorUv<P : PathHolder>(
private val executableFlow = MutableStateFlow(model.uvViewModel.uvExecutable.get())
private val pythonVersion: ObservableMutableProperty<Version?> = propertyGraph.property(null)
private lateinit var versionComboBox: ComboBox<Version?>
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?> = model.uvViewModel.uvExecutable
override val toolExecutablePersister: suspend (P) -> Unit = { pathHolder ->
savePathForEelOnly(pathHolder) { path -> setUvExecutable(path) }
}
private val loading = AtomicBooleanProperty(false)
init {
toolValidator.backProperty.afterChange {
model.uvViewModel.uvExecutable.afterChange {
executableFlow.value = it
}
}
@@ -176,16 +180,6 @@ internal class EnvironmentCreatorUv<P : PathHolder>(
}
}
override suspend fun savePathToExecutableToProperties(pathHolder: PathHolder?) {
if ((model.fileSystem as? FileSystem.Eel)?.eelApi !is LocalEelApi) return
val savingPath = (pathHolder as? PathHolder.Eel)?.path
?: (toolValidator.backProperty.get()?.pathHolder as? PathHolder.Eel)?.path
savingPath?.let {
setUvExecutable(it)
}
}
override suspend fun setupEnvSdk(
moduleBasePath: Path,
baseSdks: List<Sdk>,

View File

@@ -2,6 +2,7 @@
package com.jetbrains.python.sdk.add.v2.uv
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
import com.jetbrains.python.PyBundle
@@ -15,6 +16,7 @@ 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.impl.setUvExecutable
import com.jetbrains.python.sdk.uv.isUv
import com.jetbrains.python.sdk.uv.setupExistingEnvAndSdk
import com.jetbrains.python.statistics.InterpreterType
@@ -29,8 +31,12 @@ 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
override val interpreterType: InterpreterType = InterpreterType.UV
override val toolState: ToolValidator<P> = model.uvViewModel.toolValidator
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?> = model.uvViewModel.uvExecutable
override val toolExecutablePersister: suspend (P) -> Unit = { pathHolder ->
savePathForEelOnly(pathHolder) { path -> setUvExecutable(path) }
}
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): PyResult<Sdk> {
val sdkHomePath = selectedEnv.get()?.homePath

View File

@@ -3,11 +3,14 @@ package com.jetbrains.python.sdk.add.v2.venv
import com.intellij.openapi.application.EDT
import com.intellij.openapi.observable.properties.ObservableMutableProperty
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.observable.util.isNotNull
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.ui.components.ActionLink
import com.intellij.ui.dsl.builder.*
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.components.validationTooltip
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.errorProcessing.PyResult
@@ -26,6 +29,8 @@ import kotlinx.coroutines.plus
class EnvironmentCreatorVenv<P : PathHolder>(model: PythonMutableTargetAddInterpreterModel<P>) : PythonNewEnvironmentCreator<P>(model) {
private lateinit var versionComboBox: PythonInterpreterComboBox<P>
private lateinit var venvPathField: ValidatedPathField<Unit, P, ValidatedPath.Folder<P>>
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?>? = null
override val toolExecutablePersister: suspend (P) -> Unit = { }
private val venvAlreadyExistsError = propertyGraph.property<VenvAlreadyExistsError<P>?>(null)
private val venvAlreadyExistsErrorMessage: ObservableMutableProperty<String> = propertyGraph.property("")

View File

@@ -2,6 +2,7 @@
package com.jetbrains.python.sdk.add.v2.venv
import com.intellij.openapi.module.Module
import com.intellij.openapi.observable.properties.ObservableProperty
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.ui.dsl.builder.Panel
@@ -9,24 +10,16 @@ import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.add.v2.PathHolder
import com.jetbrains.python.sdk.add.v2.PythonAddInterpreterModel
import com.jetbrains.python.sdk.add.v2.PythonExistingEnvironmentConfigurator
import com.jetbrains.python.sdk.add.v2.PythonInterpreterComboBox
import com.jetbrains.python.sdk.add.v2.PythonInterpreterCreationTargets
import com.jetbrains.python.sdk.add.v2.existingSdks
import com.jetbrains.python.sdk.add.v2.pythonInterpreterComboBox
import com.jetbrains.python.sdk.add.v2.setupSdk
import com.jetbrains.python.sdk.add.v2.sortForExistingEnvironment
import com.jetbrains.python.sdk.add.v2.toStatisticsField
import com.jetbrains.python.sdk.add.v2.*
import com.jetbrains.python.statistics.InterpreterCreationMode
import com.jetbrains.python.statistics.InterpreterType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.map
class PythonExistingEnvironmentSelector<P: PathHolder>(model: PythonAddInterpreterModel<P>, private val module: Module?) : PythonExistingEnvironmentConfigurator<P>(model) {
class PythonExistingEnvironmentSelector<P : PathHolder>(model: PythonAddInterpreterModel<P>, private val module: Module?) : PythonExistingEnvironmentConfigurator<P>(model) {
private lateinit var comboBox: PythonInterpreterComboBox<P>
override val toolExecutable: ObservableProperty<ValidatedPath.Executable<P>?>? = null
override val toolExecutablePersister: suspend (P) -> Unit = { }
override fun setupUI(panel: Panel, validationRequestor: DialogValidationRequestor) {
with(panel) {