diff --git a/python/pluginCore/resources/META-INF/plugin.xml b/python/pluginCore/resources/META-INF/plugin.xml
index da161da61548..c0ae2eae69a6 100644
--- a/python/pluginCore/resources/META-INF/plugin.xml
+++ b/python/pluginCore/resources/META-INF/plugin.xml
@@ -626,6 +626,8 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
+
diff --git a/python/python-sdk/src/com/jetbrains/python/sdk/Sdks.kt b/python/python-sdk/src/com/jetbrains/python/sdk/Sdks.kt
index ce69781f5b65..9f953ba277df 100644
--- a/python/python-sdk/src/com/jetbrains/python/sdk/Sdks.kt
+++ b/python/python-sdk/src/com/jetbrains/python/sdk/Sdks.kt
@@ -21,7 +21,7 @@ import java.net.URL
import java.nio.charset.StandardCharsets
-private val LOG: Logger = logger()
+val LOG: Logger = logger()
/**
diff --git a/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt b/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt
index 94cc1e681f79..62fb43e9310e 100644
--- a/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt
+++ b/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt
@@ -15,6 +15,7 @@ import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.util.registry.Registry
import com.jetbrains.python.PyBundle
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
@@ -22,6 +23,7 @@ import com.jetbrains.python.run.allowCreationTargetOfThisType
import com.jetbrains.python.sdk.add.PyAddSdkDialog
import com.jetbrains.python.sdk.add.collector.PythonNewInterpreterAddedCollector
import com.jetbrains.python.sdk.add.target.PyAddTargetBasedSdkDialog
+import com.jetbrains.python.sdk.add.v2.PythonAddLocalInterpreterDialog
import com.jetbrains.python.target.PythonLanguageRuntimeType
import java.util.function.Consumer
@@ -54,8 +56,12 @@ private class AddLocalInterpreterAction(private val project: Project,
private val onSdkCreated: Consumer)
: AnAction(PyBundle.messagePointer("python.sdk.action.add.local.interpreter.text"), AllIcons.Nodes.HomeFolder), DumbAware {
override fun actionPerformed(e: AnActionEvent) {
- val model = PyConfigurableInterpreterList.getInstance(project).model
+ if (Registry.`is`("python.unified.interpreter.configuration")) {
+ PythonAddLocalInterpreterDialog(project).show()
+ return
+ }
+ val model = PyConfigurableInterpreterList.getInstance(project).model
PyAddTargetBasedSdkDialog.show(
project,
module,
diff --git a/python/src/com/jetbrains/python/sdk/PySdkExt.kt b/python/src/com/jetbrains/python/sdk/PySdkExt.kt
index 7cd65e3b38e3..cefa6d13a3d3 100644
--- a/python/src/com/jetbrains/python/sdk/PySdkExt.kt
+++ b/python/src/com/jetbrains/python/sdk/PySdkExt.kt
@@ -67,7 +67,7 @@ import java.nio.file.Paths
import kotlin.io.path.div
import kotlin.io.path.pathString
-private data class TargetAndPath(
+internal data class TargetAndPath(
val target: TargetEnvironmentConfiguration?,
val path: FullPathOnTarget?,
)
@@ -127,19 +127,24 @@ fun detectSystemWideSdks(
{ it.homePath }).reversed())
}
-private fun PythonSdkFlavor<*>.detectSdks(
- module: Module?,
- context: UserDataHolder,
- targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
- existingPaths: HashSet,
-): List =
+private fun PythonSdkFlavor<*>.detectSdks(module: Module?,
+ context: UserDataHolder,
+ targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
+ existingPaths: HashSet): List =
+ detectSdkPaths(module, context, targetModuleSitsOn, existingPaths)
+ .map { createDetectedSdk(it, targetModuleSitsOn?.asTargetConfig, this) }
+
+
+internal fun PythonSdkFlavor<*>.detectSdkPaths(module: Module?,
+ context: UserDataHolder,
+ targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
+ existingPaths: HashSet): List =
suggestLocalHomePaths(module, context)
.mapNotNull {
// If module sits on target, this target maps its path.
if (targetModuleSitsOn == null) it.pathString else targetModuleSitsOn.getTargetPathIfLocalPathIsOnTarget(it)
}
.filter { TargetAndPath(targetModuleSitsOn?.asTargetConfig, it) !in existingPaths }
- .map { createDetectedSdk(it, targetModuleSitsOn?.asTargetConfig, this) }
fun resetSystemWideSdksDetectors() {
PythonSdkFlavor.getApplicableFlavors(false).forEach(PythonSdkFlavor<*>::dropCaches)
@@ -365,7 +370,7 @@ fun getInnerVirtualEnvRoot(sdk: Sdk): VirtualFile? {
}
}
-private fun suggestAssociatedSdkName(sdkHome: String, associatedPath: String?): String? {
+internal fun suggestAssociatedSdkName(sdkHome: String, associatedPath: String?): String? {
// please don't forget to update com.jetbrains.python.inspections.PyInterpreterInspection.Visitor#getSuitableSdkFix
// after changing this method
@@ -385,7 +390,7 @@ private fun suggestAssociatedSdkName(sdkHome: String, associatedPath: String?):
return "$baseSdkName ($associatedName)"
}
-private val Sdk.isSystemWide: Boolean
+internal val Sdk.isSystemWide: Boolean
get() = !PythonSdkUtil.isRemote(this) && !PythonSdkUtil.isVirtualEnv(
this) && !PythonSdkUtil.isCondaVirtualEnv(this)
diff --git a/python/src/com/jetbrains/python/sdk/PySdks.kt b/python/src/com/jetbrains/python/sdk/PySdks.kt
index 11578cf2575a..69fee786b7fe 100644
--- a/python/src/com/jetbrains/python/sdk/PySdks.kt
+++ b/python/src/com/jetbrains/python/sdk/PySdks.kt
@@ -65,6 +65,19 @@ private suspend fun detectSystemWideSdksSuspended(module: Module?,
{ it.homePath }).reversed())
}
+private suspend fun detectSystemWideInterpreters(module: Module?,
+ existingSdks: List,
+ target: TargetEnvironmentConfiguration? = null,
+ context: UserDataHolder): List {
+ if (module != null && module.isDisposed) return emptyList()
+ val effectiveTarget = target ?: module?.let { PythonInterpreterTargetEnvironmentFactory.getTargetModuleResidesOn(it) }?.asTargetConfig
+ val baseDirFromContext = context.getUserData(BASE_DIR)
+ return service().getOrDetectSdks(effectiveTarget, baseDirFromContext)
+ .filter { detectedSdk -> existingSdks.none(detectedSdk::isSameAs) }
+ .sortedWith(compareBy({ it.guessedLanguageLevel },
+ { it.homePath }).reversed())
+}
+
private fun Sdk.isSameAs(another: Sdk): Boolean =
targetEnvConfiguration == another.targetEnvConfiguration && homePath == another.homePath
@@ -127,7 +140,7 @@ private fun tryFindBaseSdksOnTarget(targetEnvironmentConfiguration: TargetEnviro
private val PYTHON_INTERPRETER_NAME_UNIX_PATTERN = Pattern.compile("python\\d(\\.\\d+)")
-private fun Path.tryFindPythonBinaries(): List =
+internal fun Path.tryFindPythonBinaries(): List =
runCatching { Files.list(this).filter(Path::looksLikePythonBinary).collect(Collectors.toList()) }.getOrElse { emptyList() }
private fun Path.looksLikePythonBinary(): Boolean =
diff --git a/python/src/com/jetbrains/python/sdk/add/target/PyAddTargetBasedSdkDialog.kt b/python/src/com/jetbrains/python/sdk/add/target/PyAddTargetBasedSdkDialog.kt
index 7a7dcc8c2346..00365005a444 100644
--- a/python/src/com/jetbrains/python/sdk/add/target/PyAddTargetBasedSdkDialog.kt
+++ b/python/src/com/jetbrains/python/sdk/add/target/PyAddTargetBasedSdkDialog.kt
@@ -29,7 +29,7 @@ class PyAddTargetBasedSdkDialog private constructor(private val project: Project
centerPanel = PyAddTargetBasedSdkPanel(project, module, existingSdks, targetEnvironmentConfiguration?.let { { it } },
config = PythonLanguageRuntimeConfiguration(),
introspectable = null).apply {
- Disposer.register(disposable, this)
+ Disposer.register(disposable, this)
}
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/CondaExistingEnvironmentSelector.kt b/python/src/com/jetbrains/python/sdk/add/v2/CondaExistingEnvironmentSelector.kt
index 03bfb3bec8ba..c0bedbd645bf 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/CondaExistingEnvironmentSelector.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/CondaExistingEnvironmentSelector.kt
@@ -1,11 +1,9 @@
// Copyright 2000-2023 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
-import com.intellij.execution.target.FullPathOnTarget
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
-import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.observable.util.notEqualsTo
import com.intellij.openapi.projectRoots.Sdk
@@ -16,24 +14,18 @@ import com.intellij.ui.dsl.builder.bindItem
import com.intellij.ui.layout.predicate
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
-import com.jetbrains.python.sdk.add.WslContext
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
import com.jetbrains.python.statistics.InterpreterCreationMode
-import com.jetbrains.python.statistics.InterpreterTarget
import com.jetbrains.python.statistics.InterpreterType
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.debounce
-import kotlin.time.Duration.Companion.seconds
@OptIn(FlowPreview::class)
-class CondaExistingEnvironmentSelector(presenter: PythonAddInterpreterPresenter) : PythonAddEnvironment(presenter) {
+class CondaExistingEnvironmentSelector(model: PythonAddInterpreterModel) : PythonExistingEnvironmentConfigurator(model) {
private lateinit var envComboBox: ComboBox
- private val selectedEnvironment = propertyGraph.property(null)
private val lastLoadedConda = propertyGraph.property("")
- private val loadingCondaEnvironments = MutableStateFlow(value = false)
override fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor) {
with(panel) {
@@ -41,16 +33,16 @@ class CondaExistingEnvironmentSelector(presenter: PythonAddInterpreterPresenter)
validationRequestor,
message("sdk.create.conda.executable.path"),
message("sdk.create.conda.missing.text"),
- createInstallCondaFix(presenter))
- .displayLoaderWhen(presenter.detectingCondaExecutable, scope = presenter.scope, uiContext = presenter.uiContext)
+ createInstallCondaFix(model))
+ .displayLoaderWhen(model.condaEnvironmentsLoading, scope = model.scope, uiContext = model.uiContext)
row(message("sdk.create.custom.env.creation.type")) {
- val condaEnvironmentsLoaded = loadingCondaEnvironments.predicate(presenter.scope) { !it }
+ val condaEnvironmentsLoaded = model.condaEnvironmentsLoading.predicate(model.scope) { !it }
envComboBox = comboBox(emptyList(), CondaEnvComboBoxListCellRenderer())
- .bindItem(selectedEnvironment)
- .displayLoaderWhen(loadingCondaEnvironments, makeTemporaryEditable = true,
- scope = presenter.scope, uiContext = presenter.uiContext)
+ .bindItem(state.selectedCondaEnv)
+ .displayLoaderWhen(model.condaEnvironmentsLoading, makeTemporaryEditable = true,
+ scope = model.scope, uiContext = model.uiContext)
.component
link(message("sdk.create.custom.conda.refresh.envs"), action = { onReloadCondaEnvironments() })
@@ -60,73 +52,67 @@ class CondaExistingEnvironmentSelector(presenter: PythonAddInterpreterPresenter)
}
private fun onReloadCondaEnvironments() {
- val modalityState = ModalityState.current().asContextElement()
- state.scope.launch(Dispatchers.EDT + modalityState) {
- reloadCondaEnvironments(presenter.condaExecutableOnTarget)
- }
- }
-
- private suspend fun reloadCondaEnvironments(condaExecutableOnTarget: FullPathOnTarget) {
- try {
- loadingCondaEnvironments.value = true
- val commandExecutor = presenter.createExecutor()
- val environments = PyCondaEnv.getEnvs(commandExecutor, condaExecutableOnTarget)
- envComboBox.removeAllItems()
- val envs = environments.getOrLogException(LOG) ?: emptyList()
- selectedEnvironment.set(envs.firstOrNull())
- envs.forEach(envComboBox::addItem)
- lastLoadedConda.set(state.condaExecutable.get())
- }
- finally {
- loadingCondaEnvironments.value = false
+ model.scope.launch(Dispatchers.EDT + ModalityState.current().asContextElement()) {
+ model.condaEnvironmentsLoading.value = true
+ model.detectCondaEnvironments()
+ model.condaEnvironmentsLoading.value = false
}
}
override fun onShown() {
- val modalityState = ModalityState.current().asContextElement()
- state.scope.launch(start = CoroutineStart.UNDISPATCHED) {
- presenter.currentCondaExecutableFlow
- .debounce(1.seconds)
- .collectLatest { condaExecutablePath ->
- withContext(Dispatchers.EDT + modalityState) {
- val pathOnTarget = condaExecutablePath?.let { presenter.getPathOnTarget(it) }
- if (pathOnTarget != null) {
- reloadCondaEnvironments(pathOnTarget)
- }
- else {
- loadingCondaEnvironments.value = false
- }
- }
- }
- }
-
- state.scope.launch(start = CoroutineStart.UNDISPATCHED) {
- presenter.currentCondaExecutableFlow.collectLatest {
- loadingCondaEnvironments.value = true
+ model.scope.launch(start = CoroutineStart.UNDISPATCHED) {
+ model.condaEnvironments.collectLatest { environments ->
+ envComboBox.removeAllItems()
+ environments.forEach(envComboBox::addItem)
}
}
- state.scope.launch(start = CoroutineStart.UNDISPATCHED) {
- presenter.detectingCondaExecutable.collectLatest { isDetecting ->
- if (isDetecting) loadingCondaEnvironments.value = true
- }
- }
+
+ //model.scope.launch(start = CoroutineStart.UNDISPATCHED) {
+ // presenter.currentCondaExecutableFlow
+ // .debounce(1.seconds)
+ // .collectLatest { condaExecutablePath ->
+ // withContext(Dispatchers.EDT + modalityState) {
+ // val pathOnTarget = condaExecutablePath?.let { presenter.getPathOnTarget(it) }
+ // if (pathOnTarget != null) {
+ // reloadCondaEnvironments(pathOnTarget)
+ // }
+ // else {
+ // loadingCondaEnvironments.value = false
+ // }
+ // }
+ // }
+ //}
+
+ //state.scope.launch(start = CoroutineStart.UNDISPATCHED) {
+ // presenter.currentCondaExecutableFlow.collectLatest {
+ // loadingCondaEnvironments.value = true
+ // }
+ //}
+ //
+ //state.scope.launch(start = CoroutineStart.UNDISPATCHED) {
+ // presenter.detectingCondaExecutable.collectLatest { isDetecting ->
+ // if (isDetecting) loadingCondaEnvironments.value = true
+ // }
+ //}
}
override fun getOrCreateSdk(): Sdk {
- return presenter.selectCondaEnvironment(selectedEnvironment.get()!!.envIdentity)
+ return model.selectCondaEnvironment(state.selectedCondaEnv.get()!!.envIdentity)
}
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo {
- val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
- val identity = selectedEnvironment.get()?.envIdentity as? PyCondaEnvIdentity.UnnamedEnv
+ //val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ val statisticsTarget = target.toStatisticsField()
+ val identity = model.state.selectedCondaEnv.get()?.envIdentity as? PyCondaEnvIdentity.UnnamedEnv
val selectedConda = if (identity?.isBase == true) InterpreterType.BASE_CONDA else InterpreterType.CONDAVENV
return InterpreterStatisticsInfo(selectedConda,
statisticsTarget,
false,
false,
true,
- presenter.projectLocationContext is WslContext,
+ //presenter.projectLocationContext is WslContext,
+ false,
InterpreterCreationMode.CUSTOM)
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/CondaNewEnvironmentCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/CondaNewEnvironmentCreator.kt
index 87bab778f207..bc13157828d1 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/CondaNewEnvironmentCreator.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/CondaNewEnvironmentCreator.kt
@@ -20,9 +20,8 @@ import com.jetbrains.python.statistics.InterpreterTarget
import com.jetbrains.python.statistics.InterpreterType
import java.io.File
-class CondaNewEnvironmentCreator(presenter: PythonAddInterpreterPresenter) : PythonAddEnvironment(presenter) {
+class CondaNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel) : PythonNewEnvironmentCreator(model) {
- private val envName = propertyGraph.property("")
private lateinit var pythonVersion: ObservableMutableProperty
private lateinit var versionComboBox: ComboBox
@@ -36,34 +35,36 @@ class CondaNewEnvironmentCreator(presenter: PythonAddInterpreterPresenter) : Pyt
}
row(message("sdk.create.custom.conda.env.name")) {
textField()
- .bindText(envName)
+ .bindText(model.state.newCondaEnvName)
}
- executableSelector(state.condaExecutable,
+ executableSelector(model.state.condaExecutable,
validationRequestor,
message("sdk.create.conda.executable.path"),
message("sdk.create.conda.missing.text"),
- createInstallCondaFix(presenter))
- .displayLoaderWhen(presenter.detectingCondaExecutable, scope = presenter.scope, uiContext = presenter.uiContext)
+ createInstallCondaFix(model))
+ .displayLoaderWhen(model.condaEnvironmentsLoading, scope = model.scope, uiContext = model.uiContext)
}
}
override fun onShown() {
- envName.set(state.projectPath.get().substringAfterLast(File.separator))
+ model.state.newCondaEnvName.set(model.projectPath.get().substringAfterLast(File.separator))
}
override fun getOrCreateSdk(): Sdk? {
- return presenter.createCondaEnvironment(NewCondaEnvRequest.EmptyNamedEnv(pythonVersion.get(), envName.get()))
+ return model.createCondaEnvironment(NewCondaEnvRequest.EmptyNamedEnv(pythonVersion.get(), model.state.newCondaEnvName.get()))
}
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo {
- val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ //val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ val statisticsTarget = target.toStatisticsField() // todo fix for wsl
return InterpreterStatisticsInfo(InterpreterType.CONDAVENV,
statisticsTarget,
false,
false,
false,
- presenter.projectLocationContext is WslContext,
+ //presenter.projectLocationContext is WslContext,
+ false, // todo fix for wsl
InterpreterCreationMode.CUSTOM)
}
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PipEnvNewEnvironmentCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/PipEnvNewEnvironmentCreator.kt
index 0d0450b78e97..f99ff1673e6c 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PipEnvNewEnvironmentCreator.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PipEnvNewEnvironmentCreator.kt
@@ -2,90 +2,89 @@
package com.jetbrains.python.sdk.add.v2
import com.intellij.ide.util.PropertiesComponent
-import com.intellij.openapi.application.EDT
-import com.intellij.openapi.application.ModalityState
-import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
-import com.intellij.openapi.ui.ComboBox
-import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.Panel
import com.intellij.util.text.nullize
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
-import com.jetbrains.python.sdk.add.WslContext
-import com.jetbrains.python.sdk.pipenv.detectPipEnvExecutable
import com.jetbrains.python.sdk.pipenv.pipEnvPath
import com.jetbrains.python.sdk.pipenv.setupPipEnvSdkUnderProgress
import com.jetbrains.python.statistics.InterpreterCreationMode
-import com.jetbrains.python.statistics.InterpreterTarget
import com.jetbrains.python.statistics.InterpreterType
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-class PipEnvNewEnvironmentCreator(presenter: PythonAddInterpreterPresenter) : PythonAddEnvironment(presenter) {
- private val executable = propertyGraph.property(UNKNOWN_EXECUTABLE)
- private val basePythonVersion = propertyGraph.property(initial = null)
- private lateinit var pipEnvPathField: TextFieldWithBrowseButton
- private lateinit var basePythonComboBox: ComboBox
+class PipEnvNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel) : PythonNewEnvironmentCreator(model) {
+
+ private lateinit var basePythonComboBox: PythonInterpreterComboBox
override fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor) {
with(panel) {
row(message("sdk.create.custom.base.python")) {
- basePythonComboBox = pythonInterpreterComboBox(basePythonVersion,
- presenter,
- presenter.basePythonSdksFlow,
- presenter::addBasePythonInterpreter)
+ basePythonComboBox = pythonInterpreterComboBox(model.state.baseInterpreter,
+ model,
+ model::addInterpreter,
+ model.interpreterLoading)
.align(Align.FILL)
.component
}
- pipEnvPathField = executableSelector(executable,
- validationRequestor,
- message("sdk.create.custom.pipenv.path"),
- message("sdk.create.custom.pipenv.missing.text")).component
+ executableSelector(model.state.pipenvExecutable,
+ validationRequestor,
+ message("sdk.create.custom.pipenv.path"),
+ message("sdk.create.custom.pipenv.missing.text")).component
}
}
override fun onShown() {
- val savedPath = PropertiesComponent.getInstance().pipEnvPath
- if (savedPath != null) {
- executable.set(savedPath)
- }
- else {
- val modalityState = ModalityState.current().asContextElement()
- state.scope.launch(Dispatchers.IO) {
- val detectedExecutable = detectPipEnvExecutable()
- withContext(Dispatchers.EDT + modalityState) {
- detectedExecutable?.let { executable.set(it.path) }
- }
- }
- }
+ basePythonComboBox.setItems(model.baseInterpreters)
+
+ //val savedPath = PropertiesComponent.getInstance().pipEnvPath
+ //if (savedPath != null) {
+ // model.state.pipenvExecutable.set(savedPath)
+ //}
+ //else {
+ // val modalityState = ModalityState.current().asContextElement()
+ // model.scope.launch(Dispatchers.IO) {
+ // val detectedExecutable = detectPipEnvExecutable()
+ // withContext(Dispatchers.EDT + modalityState) {
+ // detectedExecutable?.let { model.state.pipenvExecutable.set(it.path) }
+ // }
+ // }
+ //}
}
override fun getOrCreateSdk(): Sdk? {
- PropertiesComponent.getInstance().pipEnvPath = pipEnvPathField.text.nullize()
- val baseSdk = installBaseSdk(basePythonVersion.get()!!, state.allSdks.get()) ?: return null
- val newSdk = setupPipEnvSdkUnderProgress(null, null, state.basePythonSdks.get(), state.projectPath.get(),
- baseSdk.homePath, false)!!
+ if (model is PythonLocalAddInterpreterModel) {
+ PropertiesComponent.getInstance().pipEnvPath = model.state.pipenvExecutable.get().nullize()
+ }
+
+ // todo think about better error handling
+ val selectedBasePython = model.state.baseInterpreter.get()!!
+ val homePath = model.installPythonIfNeeded(selectedBasePython)
+
+ val newSdk = setupPipEnvSdkUnderProgress(null, null,
+ model.baseSdks,
+ model.projectPath.get(),
+ homePath, false)!!
SdkConfigurationUtil.addSdk(newSdk)
return newSdk
}
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo {
- val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ //val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ val statisticsTarget = target.toStatisticsField() // todo fix for wsl
return InterpreterStatisticsInfo(InterpreterType.PIPENV,
statisticsTarget,
false,
false,
false,
- presenter.projectLocationContext is WslContext,
+ //presenter.projectLocationContext is WslContext,
+ false, // todo fix for wsl
InterpreterCreationMode.CUSTOM)
}
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PoetryNewEnvironmentCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/PoetryNewEnvironmentCreator.kt
index a56463f2d4ab..511c831d5715 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PoetryNewEnvironmentCreator.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PoetryNewEnvironmentCreator.kt
@@ -2,46 +2,35 @@
package com.jetbrains.python.sdk.add.v2
import com.intellij.ide.util.PropertiesComponent
-import com.intellij.openapi.application.EDT
-import com.intellij.openapi.application.ModalityState
-import com.intellij.openapi.application.asContextElement
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
-import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.Panel
import com.intellij.util.text.nullize
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
-import com.jetbrains.python.sdk.add.WslContext
-import com.jetbrains.python.sdk.poetry.detectPoetryExecutable
import com.jetbrains.python.sdk.poetry.poetryPath
import com.jetbrains.python.sdk.poetry.setupPoetrySdkUnderProgress
import com.jetbrains.python.statistics.InterpreterCreationMode
-import com.jetbrains.python.statistics.InterpreterTarget
import com.jetbrains.python.statistics.InterpreterType
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-class PoetryNewEnvironmentCreator(presenter: PythonAddInterpreterPresenter) : PythonAddEnvironment(presenter) {
+class PoetryNewEnvironmentCreator(model: PythonMutableTargetAddInterpreterModel) : PythonNewEnvironmentCreator(model) {
+
+ private lateinit var basePythonComboBox: PythonInterpreterComboBox
- val executable = propertyGraph.property(UNKNOWN_EXECUTABLE)
- private val basePythonVersion = propertyGraph.property(initial = null)
- private lateinit var basePythonComboBox: ComboBox
override fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor) {
with(panel) {
row(message("sdk.create.custom.base.python")) {
- basePythonComboBox = pythonInterpreterComboBox(basePythonVersion,
- presenter,
- presenter.basePythonSdksFlow,
- presenter::addBasePythonInterpreter)
- .align(Align.FILL)
- .component
+ basePythonComboBox = pythonInterpreterComboBox(model.state.baseInterpreter,
+ model,
+ model::addInterpreter,
+ model.interpreterLoading)
+ .align(Align.FILL)
+ .component
}
- executableSelector(executable,
+ executableSelector(model.state.poetryExecutable,
validationRequestor,
message("sdk.create.custom.poetry.path"),
message("sdk.create.custom.poetry.missing.text")).component
@@ -49,39 +38,51 @@ class PoetryNewEnvironmentCreator(presenter: PythonAddInterpreterPresenter) : Py
}
override fun onShown() {
- val savedPath = PropertiesComponent.getInstance().poetryPath
- if (savedPath != null) {
- executable.set(savedPath)
- }
- else {
- val modalityState = ModalityState.current().asContextElement()
- state.scope.launch(Dispatchers.IO) {
- val poetryExecutable = detectPoetryExecutable()
- withContext(Dispatchers.EDT + modalityState) {
- poetryExecutable?.let { executable.set(it.path) }
- }
- }
- }
+ basePythonComboBox.setItems(model.baseInterpreters)
+
+ //val savedPath = PropertiesComponent.getInstance().poetryPath
+ //if (savedPath != null) {
+ // model.state.poetryExecutable.set(savedPath)
+ //}
+ //else {
+ // val modalityState = ModalityState.current().asContextElement()
+ // model.scope.launch(Dispatchers.IO) {
+ // val poetryExecutable = detectPoetryExecutable()
+ // withContext(Dispatchers.EDT + modalityState) {
+ // poetryExecutable?.let { model.state.poetryExecutable.set(it.path) }
+ // }
+ // }
+ //}
}
override fun getOrCreateSdk(): Sdk? {
- PropertiesComponent.getInstance().poetryPath = executable.get().nullize()
- val baseSdk = installBaseSdk(basePythonVersion.get()!!, state.allSdks.get()) ?: return null
- val newSdk = setupPoetrySdkUnderProgress(null, null, state.basePythonSdks.get(), state.projectPath.get(),
- baseSdk.homePath, false)!!
+ if (model is PythonLocalAddInterpreterModel) {
+ PropertiesComponent.getInstance().poetryPath = model.state.poetryExecutable.get().nullize()
+ }
+
+ val selectedBasePython = model.state.baseInterpreter.get()!!
+ val homePath = model.installPythonIfNeeded(selectedBasePython)
+
+ val newSdk = setupPoetrySdkUnderProgress(null,
+ null,
+ model.baseSdks,
+ model.projectPath.get(),
+ homePath, false)!!
SdkConfigurationUtil.addSdk(newSdk)
return newSdk
}
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo {
- val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ //val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ val statisticsTarget = target.toStatisticsField() // todo fix for wsl
return InterpreterStatisticsInfo(InterpreterType.POETRY,
statisticsTarget,
false,
false,
false,
- presenter.projectLocationContext is WslContext,
+ //presenter.projectLocationContext is WslContext,
+ false, // todo fix for wsl
InterpreterCreationMode.CUSTOM)
}
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt
index 21b357641fee..72555f34159e 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddCustomInterpreter.kt
@@ -1,56 +1,129 @@
// Copyright 2000-2023 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
+import com.intellij.openapi.observable.util.and
+import com.intellij.openapi.observable.util.equalsTo
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.validation.DialogValidationRequestor
+import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
+import com.intellij.openapi.ui.validation.and
import com.intellij.ui.dsl.builder.Panel
+import com.intellij.ui.dsl.builder.bind
+import com.intellij.ui.dsl.builder.bindItem
+import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.sdk.add.v2.PythonInterpreterCreationTargets.LOCAL_MACHINE
+import com.jetbrains.python.sdk.add.v2.PythonSupportedEnvironmentManagers.*
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import java.awt.Component
-class PythonAddCustomInterpreter(presenter: PythonAddInterpreterPresenter) {
+class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterModel) {
- //private lateinit var targetSelector: ComboBox
+ private val propertyGraph = model.propertyGraph
+ private val selectionMethod = propertyGraph.property(PythonInterpreterSelectionMethod.CREATE_NEW)
+ private val _createNew = propertyGraph.booleanProperty(selectionMethod, PythonInterpreterSelectionMethod.CREATE_NEW)
+ private val _selectExisting = propertyGraph.booleanProperty(selectionMethod, PythonInterpreterSelectionMethod.SELECT_EXISTING)
+ private val newInterpreterManager = propertyGraph.property(VIRTUALENV)
+ private val existingInterpreterManager = propertyGraph.property(PYTHON)
- private val targets = mapOf(
- LOCAL_MACHINE to PythonLocalEnvironmentCreator(presenter),
+ private lateinit var component: Component
+
+ private val newInterpreterCreators = mapOf(
+ VIRTUALENV to PythonNewVirtualenvCreator(model),
+ CONDA to CondaNewEnvironmentCreator(model),
+ PIPENV to PipEnvNewEnvironmentCreator(model),
+ POETRY to PoetryNewEnvironmentCreator(model),
)
+ private val existingInterpreterSelectors = mapOf(
+ PYTHON to PythonExistingEnvironmentSelector(model),
+ CONDA to CondaExistingEnvironmentSelector(model),
+ )
+
+ private val currentSdkManager: PythonAddEnvironment
+ get() {
+ return if (_selectExisting.get()) existingInterpreterSelectors[existingInterpreterManager.get()]!!
+ else newInterpreterCreators[newInterpreterManager.get()]!!
+ }
+
+
fun buildPanel(outerPanel: Panel, validationRequestor: DialogValidationRequestor) {
+ with(model) {
+ navigator.selectionMethod = selectionMethod
+ navigator.newEnvManager = newInterpreterManager
+ navigator.existingEnvManager = existingInterpreterManager
+ }
+
+ // todo delete this. testing busy state
+ //existingInterpreterManager.afterChange {
+ // model.scope.launch {
+ // model.interpreterLoading.value = true
+ // delay(5000)
+ // model.interpreterLoading.value = false
+ // }
+ //}
+
with(outerPanel) {
+ buttonsGroup {
+ row(message("sdk.create.custom.env.creation.type")) {
+ val newRadio = radioButton(message("sdk.create.custom.generate.new"), PythonInterpreterSelectionMethod.CREATE_NEW).onChanged {
+ selectionMethod.set(
+ if (it.isSelected) PythonInterpreterSelectionMethod.CREATE_NEW else PythonInterpreterSelectionMethod.SELECT_EXISTING)
+ }.component
- // todo uncomment for all available targets
- //row(message("sdk.create.custom.develop.on")) {
- // targetSelector = comboBox(targets.keys, PythonEnvironmentComboBoxRenderer())
- // .widthGroup("env_aligned")
- // .component
- //}
- //targets.forEach { target ->
- // rowsRange {
- // target.value.buildPanel(this)
- // }.visibleIf(targetSelector.selectedValueMatches { it == target.key })
- //}
+ val existingRadio = radioButton(message("sdk.create.custom.select.existing"), PythonInterpreterSelectionMethod.SELECT_EXISTING).component
+ selectionMethod.afterChange {
+ newRadio.isSelected = it == PythonInterpreterSelectionMethod.CREATE_NEW
+ existingRadio.isSelected = it == PythonInterpreterSelectionMethod.SELECT_EXISTING
+ }
+ }
+ }.bind({ selectionMethod.get() }, { selectionMethod.set(it) })
- rowsRange {
- targets[LOCAL_MACHINE]!!.buildPanel(this, validationRequestor)
+ row(message("sdk.create.custom.type")) {
+ comboBox(newInterpreterCreators.keys, PythonEnvironmentComboBoxRenderer())
+ .bindItem(newInterpreterManager)
+ .widthGroup("env_aligned")
+ .visibleIf(_createNew)
+
+ comboBox(existingInterpreterSelectors.keys, PythonEnvironmentComboBoxRenderer())
+ .bindItem(existingInterpreterManager)
+ .widthGroup("env_aligned")
+ .visibleIf(_selectExisting)
}
+
+ newInterpreterCreators.forEach { (type, creator) ->
+ rowsRange {
+ creator.buildOptions(this,
+ validationRequestor
+ and WHEN_PROPERTY_CHANGED(selectionMethod)
+ and WHEN_PROPERTY_CHANGED(newInterpreterManager))
+ }.visibleIf(_createNew and newInterpreterManager.equalsTo(type))
+ }
+
+ existingInterpreterSelectors.forEach { (type, selector) ->
+ rowsRange {
+ selector.buildOptions(this,
+ validationRequestor
+ and WHEN_PROPERTY_CHANGED(selectionMethod)
+ and WHEN_PROPERTY_CHANGED(existingInterpreterManager))
+ }.visibleIf(_selectExisting and existingInterpreterManager.equalsTo(type))
+ }
+
}
}
+
fun onShown() {
- targets.values.forEach(PythonLocalEnvironmentCreator::onShown)
+ newInterpreterCreators.values.forEach(PythonAddEnvironment::onShown)
+ existingInterpreterSelectors.values.forEach(PythonAddEnvironment::onShown)
}
- fun getSdk(): Sdk? {
- // todo uncomment for all available targets
- //return targets[targetSelector.selectedItem]!!.getSdk()
- return targets[LOCAL_MACHINE]!!.getSdk()
- }
+ fun getSdk(): Sdk? = currentSdkManager.getOrCreateSdk()
fun createStatisticsInfo(): InterpreterStatisticsInfo {
- // todo uncomment for all available targets
- //return targets[targetSelector.selectedItem]!!.createStatisticsInfo()
- return targets[LOCAL_MACHINE]!!.createStatisticsInfo()
+ return currentSdkManager.createStatisticsInfo(PythonInterpreterCreationTargets.LOCAL_MACHINE)
}
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddInterpreterPresenter.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddInterpreterPresenter.kt
index fcfce2067490..8a7144d5eee4 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddInterpreterPresenter.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddInterpreterPresenter.kt
@@ -34,7 +34,7 @@ import java.nio.file.InvalidPathException
import java.nio.file.Path
import kotlin.coroutines.CoroutineContext
-private val emptyContext: UserDataHolder by lazy { UserDataHolderBase() }
+val emptyContext: UserDataHolder by lazy { UserDataHolderBase() }
internal fun PythonAddInterpreterPresenter.tryGetVirtualFile(pathOnTarget: FullPathOnTarget): VirtualFile? {
val mapper = targetEnvironmentConfiguration?.let { PythonInterpreterTargetEnvironmentFactory.getTargetWithMappedLocalVfs(it) }
@@ -61,7 +61,10 @@ internal fun PythonAddInterpreterPresenter.setupVirtualenv(venvPath: Path, proje
* @param state is the model for this presented in Model-View-Presenter pattern
*/
@OptIn(ExperimentalCoroutinesApi::class)
-class PythonAddInterpreterPresenter(val state: PythonAddInterpreterState, val uiContext: CoroutineContext) {
+open class PythonAddInterpreterPresenter(val state: PythonAddInterpreterState, val uiContext: CoroutineContext) {
+
+ lateinit var controller: PythonAddInterpreterModel
+
val scope: CoroutineScope
get() = state.scope
@@ -234,7 +237,7 @@ class PythonAddInterpreterPresenter(val state: PythonAddInterpreterState, val ui
data class ProjectPathWithContext(val projectPath: String, val context: ProjectLocationContext)
companion object {
- private val LOG = logger()
+ val LOG = logger()
private fun MutableStateFlow>.addDetectedSdk(targetPath: String,
targetEnvironmentConfiguration: TargetEnvironmentConfiguration?) {
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddInterpreterState.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddInterpreterState.kt
index 007677f17af1..dc8635c4e230 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddInterpreterState.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddInterpreterState.kt
@@ -8,12 +8,12 @@ import com.intellij.openapi.projectRoots.Sdk
import kotlinx.coroutines.CoroutineScope
class PythonAddInterpreterState(
- val propertyGraph: PropertyGraph,
+ val propertyGraph: PropertyGraph, // todo move to presenter
val projectPath: ObservableProperty,
val scope: CoroutineScope,
- val basePythonSdks: ObservableMutableProperty>,
- val allExistingSdks: ObservableMutableProperty>,
- val installableSdks: ObservableMutableProperty>,
+ val basePythonSdks: ObservableMutableProperty>, // todo replace with flow, local properties for every creator
+ val allExistingSdks: ObservableMutableProperty>, // todo merge with allSdks, replace with flow and local properties
+ val installableSdks: ObservableMutableProperty>, // todo not needed
val selectedVenv: ObservableMutableProperty,
val condaExecutable: ObservableMutableProperty,
) {
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddNewEnvironmentPanel.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddNewEnvironmentPanel.kt
index 80d13dd37910..3178db1fb02a 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonAddNewEnvironmentPanel.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonAddNewEnvironmentPanel.kt
@@ -13,10 +13,7 @@ import com.intellij.openapi.observable.util.or
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
-import com.intellij.ui.dsl.builder.AlignX
-import com.intellij.ui.dsl.builder.Panel
-import com.intellij.ui.dsl.builder.TopGap
-import com.intellij.ui.dsl.builder.bindText
+import com.intellij.ui.dsl.builder.*
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
@@ -49,18 +46,9 @@ class PythonAddNewEnvironmentPanel(val projectPath: ObservableProperty,
private var _projectVenv = propertyGraph.booleanProperty(selectedMode, PROJECT_VENV)
private var _baseConda = propertyGraph.booleanProperty(selectedMode, BASE_CONDA)
private var _custom = propertyGraph.booleanProperty(selectedMode, CUSTOM)
-
- private val allExistingSdks = propertyGraph.property>(emptyList())
- private val basePythonSdks = propertyGraph.property>(emptyList())
- private val installableSdks = propertyGraph.property>(emptyList())
- private val pythonBaseVersion = propertyGraph.property(null)
- private val selectedVenv = propertyGraph.property(null)
-
- private val condaExecutable = propertyGraph.property("")
private var venvHint = propertyGraph.property("")
- private lateinit var pythonBaseVersionComboBox: ComboBox
-
+ private lateinit var pythonBaseVersionComboBox: PythonInterpreterComboBox
private var initialized = false
private fun updateVenvLocationHint() {
@@ -69,23 +57,18 @@ class PythonAddNewEnvironmentPanel(val projectPath: ObservableProperty,
else if (get == BASE_CONDA && PROJECT_VENV in allowedInterpreterTypes) venvHint.set(message("sdk.create.simple.conda.hint"))
}
- val state = PythonAddInterpreterState(propertyGraph,
- projectPath,
- service().coroutineScope,
- basePythonSdks,
- allExistingSdks,
- installableSdks,
- selectedVenv,
- condaExecutable)
-
- private lateinit var presenter: PythonAddInterpreterPresenter
private lateinit var custom: PythonAddCustomInterpreter
-
+ private lateinit var model: PythonMutableTargetAddInterpreterModel
fun buildPanel(outerPanel: Panel) {
- presenter = PythonAddInterpreterPresenter(state, uiContext = Dispatchers.EDT + ModalityState.current().asContextElement())
- presenter.navigator.selectionMode = selectedMode
- custom = PythonAddCustomInterpreter(presenter)
+ //presenter = PythonAddInterpreterPresenter(state, uiContext = Dispatchers.EDT + ModalityState.current().asContextElement())
+ model = PythonLocalAddInterpreterModel(service().coroutineScope,
+ Dispatchers.EDT + ModalityState.current().asContextElement(), projectPath)
+ model.navigator.selectionMode = selectedMode
+ //presenter.controller = model
+
+ custom = PythonAddCustomInterpreter(model)
+
val validationRequestor = WHEN_PROPERTY_CHANGED(selectedMode)
@@ -98,27 +81,26 @@ class PythonAddNewEnvironmentPanel(val projectPath: ObservableProperty,
}
row(message("sdk.create.python.version")) {
- pythonBaseVersionComboBox = pythonInterpreterComboBox(pythonBaseVersion,
- presenter,
- presenter.basePythonSdksFlow,
- presenter::addBasePythonInterpreter)
+ pythonBaseVersionComboBox = pythonInterpreterComboBox(model.state.baseInterpreter,
+ model,
+ model::addInterpreter,
+ model.interpreterLoading)
.align(AlignX.FILL)
.component
}.visibleIf(_projectVenv)
rowsRange {
- executableSelector(state.condaExecutable,
+ executableSelector(model.state.condaExecutable,
validationRequestor,
message("sdk.create.conda.executable.path"),
message("sdk.create.conda.missing.text"),
- createInstallCondaFix(presenter))
- .displayLoaderWhen(presenter.detectingCondaExecutable, scope = presenter.scope, uiContext = presenter.uiContext)
+ createInstallCondaFix(model))
+ //.displayLoaderWhen(presenter.detectingCondaExecutable, scope = presenter.scope, uiContext = presenter.uiContext)
}.visibleIf(_baseConda)
-
row("") {
comment("").bindText(venvHint)
- }.visibleIf(_projectVenv or (_baseConda and state.condaExecutable.notEqualsTo(UNKNOWN_EXECUTABLE)))
+ }.visibleIf(_projectVenv or (_baseConda and model.state.condaExecutable.notEqualsTo(UNKNOWN_EXECUTABLE)))
rowsRange {
custom.buildPanel(this, validationRequestor)
@@ -133,28 +115,27 @@ class PythonAddNewEnvironmentPanel(val projectPath: ObservableProperty,
if (!initialized) {
initialized = true
val modalityState = ModalityState.current().asContextElement()
- state.scope.launch(Dispatchers.EDT + modalityState) {
- val existingSdks = PyConfigurableInterpreterList.getInstance(null).getModel().sdks.toList()
- val allValidSdks = withContext(Dispatchers.IO) {
- ProjectSpecificSettingsStep.getValidPythonSdks(existingSdks)
- }
- allExistingSdks.set(allValidSdks)
- installableSdks.set(getSdksToInstall())
+ model.scope.launch(Dispatchers.EDT + modalityState) {
+ model.initialize()
+ pythonBaseVersionComboBox.setItems(model.baseInterpreters)
+ custom.onShown()
+
updateVenvLocationHint()
}
- custom.onShown()
- presenter.navigator.restoreLastState(onlyAllowedSelectionModes = allowedInterpreterTypes)
+ // todo don't forget allowedEnvs
+ model.navigator.restoreLastState()
}
}
fun getSdk(): Sdk? {
- presenter.navigator.saveLastState()
+ model.navigator.saveLastState()
return when (selectedMode.get()) {
- PROJECT_VENV -> presenter.setupVirtualenv(Path.of(projectPath.get(), ".venv"),
- projectPath.get(),
- pythonBaseVersion.get()!!)
- BASE_CONDA -> presenter.selectCondaEnvironment(presenter.baseConda!!.envIdentity)
+ PROJECT_VENV -> model.setupVirtualenv(Path.of(projectPath.get(), ".venv"), // todo just keep venv path, all the rest is in the model
+ projectPath.get(),
+ //pythonBaseVersion.get()!!)
+ model.state.baseInterpreter.get()!!)
+ BASE_CONDA -> model.selectCondaEnvironment(model.state.baseCondaEnv.get()!!.envIdentity)
CUSTOM -> custom.getSdk()
}
}
@@ -166,14 +147,16 @@ class PythonAddNewEnvironmentPanel(val projectPath: ObservableProperty,
false,
false,
false,
- presenter.projectLocationContext is WslContext,
+ //presenter.projectLocationContext is WslContext,
+ false,
InterpreterCreationMode.SIMPLE)
BASE_CONDA -> InterpreterStatisticsInfo(InterpreterType.BASE_CONDA,
InterpreterTarget.LOCAL,
false,
false,
true,
- presenter.projectLocationContext is WslContext,
+ //presenter.projectLocationContext is WslContext,
+ false,
InterpreterCreationMode.SIMPLE)
CUSTOM -> custom.createStatisticsInfo()
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonExistingEnvironmentSelector.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonExistingEnvironmentSelector.kt
index b94c82b5448c..265f9657892a 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonExistingEnvironmentSelector.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonExistingEnvironmentSelector.kt
@@ -7,38 +7,45 @@ import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.Panel
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
-import com.jetbrains.python.sdk.add.WslContext
import com.jetbrains.python.statistics.InterpreterCreationMode
-import com.jetbrains.python.statistics.InterpreterTarget
import com.jetbrains.python.statistics.InterpreterType
-class PythonExistingEnvironmentSelector(presenter: PythonAddInterpreterPresenter) : PythonAddEnvironment(presenter) {
+class PythonExistingEnvironmentSelector(model: PythonAddInterpreterModel) : PythonExistingEnvironmentConfigurator(model) {
+
+ private lateinit var comboBox: PythonInterpreterComboBox
override fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor) {
with(panel) {
row(message("sdk.create.custom.python.path")) {
- pythonInterpreterComboBox(presenter.state.selectedVenv,
- presenter,
- presenter.allSdksFlow,
- presenter::addPythonInterpreter)
+ comboBox = pythonInterpreterComboBox(model.state.selectedInterpreter,
+ model,
+ model::addInterpreter,
+ model.interpreterLoading)
.align(Align.FILL)
+ .component
}
}
}
+ override fun onShown() {
+ comboBox.setItems(model.allInterpreters)
+ }
+
override fun getOrCreateSdk(): Sdk {
- val selectedSdk = state.selectedVenv.get() ?: error("Unknown sdk selected")
- return setupSdkIfDetected(selectedSdk, state.allSdks.get())
+ // todo error handling, nullability issues
+ return setupSdkIfDetected(model.state.selectedInterpreter.get()!!, model.existingSdks)!!
}
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo {
- val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ //val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ val statisticsTarget = target.toStatisticsField() // todo fix for wsl
return InterpreterStatisticsInfo(InterpreterType.REGULAR,
statisticsTarget,
false,
false,
true,
- presenter.projectLocationContext is WslContext,
+ //presenter.projectLocationContext is WslContext,
+ false, // todo fix for wsl
InterpreterCreationMode.CUSTOM)
}
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonLocalEnvironmentCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonLocalEnvironmentCreator.kt
index 7f3487d260eb..c70b91dec60e 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonLocalEnvironmentCreator.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonLocalEnvironmentCreator.kt
@@ -23,17 +23,19 @@ class PythonLocalEnvironmentCreator(val presenter: PythonAddInterpreterPresenter
private val newInterpreterManager = propertyGraph.property(VIRTUALENV)
private val existingInterpreterManager = propertyGraph.property(PYTHON)
- private val newInterpreterCreators = mapOf(
- VIRTUALENV to PythonNewVirtualenvCreator(presenter),
- CONDA to CondaNewEnvironmentCreator(presenter),
- PIPENV to PipEnvNewEnvironmentCreator(presenter),
- POETRY to PoetryNewEnvironmentCreator(presenter),
- )
+ private val newInterpreterCreators = emptyMap()
+ //private val newInterpreterCreators = mapOf(
+ // VIRTUALENV to PythonNewVirtualenvCreator(presenter),
+ // CONDA to CondaNewEnvironmentCreator(presenter),
+ // PIPENV to PipEnvNewEnvironmentCreator(presenter),
+ // POETRY to PoetryNewEnvironmentCreator(presenter),
+ //)
- private val existingInterpreterSelectors = mapOf(
- PYTHON to PythonExistingEnvironmentSelector(presenter),
- CONDA to CondaExistingEnvironmentSelector(presenter),
- )
+ private val existingInterpreterSelectors = emptyMap()
+ //private val existingInterpreterSelectors = mapOf(
+ // PYTHON to PythonExistingEnvironmentSelector(presenter),
+ // CONDA to CondaExistingEnvironmentSelector(presenter),
+ //)
private val currentSdkManager: PythonAddEnvironment
get() {
@@ -68,33 +70,33 @@ class PythonLocalEnvironmentCreator(val presenter: PythonAddInterpreterPresenter
row(message("sdk.create.custom.type")) {
comboBox(newInterpreterCreators.keys, PythonEnvironmentComboBoxRenderer())
- .bindItem(newInterpreterManager)
+ //.bindItem(newInterpreterManager)
.widthGroup("env_aligned")
.visibleIf(_createNew)
comboBox(existingInterpreterSelectors.keys, PythonEnvironmentComboBoxRenderer())
- .bindItem(existingInterpreterManager)
+ //.bindItem(existingInterpreterManager)
.widthGroup("env_aligned")
.visibleIf(_selectExisting)
}
- newInterpreterCreators.forEach { (type, creator) ->
- rowsRange {
- creator.buildOptions(this,
- validationRequestor
- and WHEN_PROPERTY_CHANGED(selectionMethod)
- and WHEN_PROPERTY_CHANGED(newInterpreterManager))
- }.visibleIf(_createNew and newInterpreterManager.equalsTo(type))
- }
-
- existingInterpreterSelectors.forEach { (type, selector) ->
- rowsRange {
- selector.buildOptions(this,
- validationRequestor
- and WHEN_PROPERTY_CHANGED(selectionMethod)
- and WHEN_PROPERTY_CHANGED(existingInterpreterManager))
- }.visibleIf(_selectExisting and existingInterpreterManager.equalsTo(type))
- }
+ //newInterpreterCreators.forEach { (type, creator) ->
+ // rowsRange {
+ // creator.buildOptions(this,
+ // validationRequestor
+ // and WHEN_PROPERTY_CHANGED(selectionMethod)
+ // and WHEN_PROPERTY_CHANGED(newInterpreterManager))
+ // }.visibleIf(_createNew and newInterpreterManager.equalsTo(type))
+ //}
+ //
+ //existingInterpreterSelectors.forEach { (type, selector) ->
+ // rowsRange {
+ // selector.buildOptions(this,
+ // validationRequestor
+ // and WHEN_PROPERTY_CHANGED(selectionMethod)
+ // and WHEN_PROPERTY_CHANGED(existingInterpreterManager))
+ // }.visibleIf(_selectExisting and existingInterpreterManager.equalsTo(type))
+ //}
}
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonNewVirtualenvCreator.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonNewVirtualenvCreator.kt
index 19de70e4c8f0..be4e82e29c59 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonNewVirtualenvCreator.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonNewVirtualenvCreator.kt
@@ -5,59 +5,47 @@ import com.intellij.execution.wsl.WslPath.Companion.parseWindowsUncPath
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.asContextElement
-import com.intellij.openapi.diagnostic.getOrLogException
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.projectRoots.Sdk
-import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.ui.validation.DialogValidationRequestor
import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
import com.intellij.openapi.ui.validation.and
import com.intellij.openapi.util.SystemInfo
-import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.OSAgnosticPathUtil
import com.intellij.ui.components.ActionLink
import com.intellij.ui.dsl.builder.*
import com.intellij.ui.dsl.builder.components.validationTooltip
-import com.intellij.util.PathUtil
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
import com.jetbrains.python.newProject.collector.PythonNewProjectWizardCollector
-import com.jetbrains.python.sdk.PySdkSettings
-import com.jetbrains.python.sdk.add.LocalContext
-import com.jetbrains.python.sdk.add.ProjectLocationContext
-import com.jetbrains.python.sdk.add.WslContext
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMethod.SELECT_EXISTING
+import com.jetbrains.python.sdk.add.v2.PythonSupportedEnvironmentManagers.PYTHON
import com.jetbrains.python.statistics.InterpreterCreationMode
-import com.jetbrains.python.statistics.InterpreterTarget
import com.jetbrains.python.statistics.InterpreterType
-import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import java.nio.file.InvalidPathException
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.exists
-class PythonNewVirtualenvCreator(presenter: PythonAddInterpreterPresenter) : PythonAddEnvironment(presenter) {
- private val location = propertyGraph.property("")
- private val inheritSitePackages = propertyGraph.property(false)
- private val makeAvailable = propertyGraph.property(false)
+class PythonNewVirtualenvCreator(model: PythonMutableTargetAddInterpreterModel) : PythonNewEnvironmentCreator(model) {
+ private lateinit var versionComboBox: PythonInterpreterComboBox
+
private val locationValidationFailed = propertyGraph.property(false)
private val locationValidationMessage = propertyGraph.property("Current location already exists")
- private val basePythonVersion = propertyGraph.property(initial = null)
- private lateinit var versionComboBox: ComboBox
private var locationModified = false
private var suggestedVenvName: String = ""
private var suggestedLocation: Path = Path.of("")
+
private val pythonInVenvPath: Path
get() {
return when {
- SystemInfo.isWindows && presenter.projectLocationContext !is WslContext -> Paths.get("Scripts", "python.exe")
+ SystemInfo.isWindows -> Paths.get("Scripts", "python.exe")
+ //SystemInfo.isWindows && presenter.projectLocationContext !is WslContext -> Paths.get("Scripts", "python.exe")
else -> Paths.get("bin", "python")
}
}
@@ -66,33 +54,33 @@ class PythonNewVirtualenvCreator(presenter: PythonAddInterpreterPresenter) : Pyt
val firstFixLink = ActionLink(message("sdk.create.custom.venv.use.different.venv.link", ".venv1")) {
PythonNewProjectWizardCollector.logSuggestedVenvDirFixUsed()
val newPath = suggestedLocation.resolve(suggestedVenvName)
- location.set(newPath.toString())
+ model.state.venvPath.set(newPath.toString())
}
val secondFixLink = ActionLink(message("sdk.create.custom.venv.select.existing.link")) {
PythonNewProjectWizardCollector.logExistingVenvFixUsed()
- val sdkPath = Paths.get(location.get()).resolve(pythonInVenvPath).toString()
- if (!presenter.state.allSdks.get().any { it.homePath == sdkPath }) {
- presenter.addPythonInterpreter(sdkPath)
- }
- presenter.state.selectedVenvPath.set(sdkPath)
- presenter.navigator.navigateTo(newMethod = SELECT_EXISTING, newManager = PythonSupportedEnvironmentManagers.PYTHON)
+ val sdkPath = Paths.get(model.state.venvPath.get()).resolve(pythonInVenvPath).toString()
+
+ val interpreter = model.findInterpreter(sdkPath) ?: model.addInterpreter(sdkPath)
+
+ model.state.selectedInterpreter.set(interpreter)
+ model.navigator.navigateTo(newMethod = SELECT_EXISTING, newManager = PYTHON)
}
with(panel) {
row(message("sdk.create.custom.base.python")) {
- versionComboBox = pythonInterpreterComboBox(basePythonVersion,
- presenter,
- presenter.basePythonSdksFlow,
- presenter::addBasePythonInterpreter)
- .align(Align.FILL)
- .component
+ versionComboBox = pythonInterpreterComboBox(model.state.baseInterpreter,
+ model,
+ model::addInterpreter,
+ model.interpreterLoading)
+ .align(Align.FILL)
+ .component
}
row(message("sdk.create.custom.location")) {
textFieldWithBrowseButton(message("sdk.create.custom.venv.location.browse.title"),
fileChooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor())
- .bindText(location)
+ .bindText(model.state.venvPath)
.whenTextChangedFromUi { locationModified = true }
- .validationRequestor(validationRequestor and WHEN_PROPERTY_CHANGED(location))
+ .validationRequestor(validationRequestor and WHEN_PROPERTY_CHANGED(model.state.venvPath))
.cellValidation { textField ->
addInputRule("") {
val pathExists = textField.isVisible && textField.doesPathExist()
@@ -129,38 +117,65 @@ class PythonNewVirtualenvCreator(presenter: PythonAddInterpreterPresenter) : Pyt
row("") {
checkBox(message("sdk.create.custom.inherit.packages"))
- .bindSelected(inheritSitePackages)
+ .bindSelected(model.state.inheritSitePackages)
}
row("") {
checkBox(message("sdk.create.custom.make.available"))
- .bindSelected(makeAvailable)
+ .bindSelected(model.state.makeAvailable)
}
}
- state.scope.launch(start = CoroutineStart.UNDISPATCHED) {
- presenter.projectWithContextFlow.collectLatest { (projectPath, projectLocationContext) ->
- withContext(presenter.uiContext) {
- if (!locationModified) {
- val suggestedVirtualEnvPath = runCatching {
- suggestVirtualEnvPath(projectPath, projectLocationContext)
- }.getOrLogException(LOG)
- location.set(suggestedVirtualEnvPath.orEmpty())
- }
- }
+
+ model.projectPath.afterChange {
+ if (!locationModified) {
+ val suggestedVirtualEnvPath = model.suggestVenvPath()!! // todo nullability issue
+ model.state.venvPath.set(suggestedVirtualEnvPath)
}
}
+
+ // todo venv path suggestion from controller
+ //model.scope.launch(start = CoroutineStart.UNDISPATCHED) {
+ // presenter.projectWithContextFlow.collectLatest { (projectPath, projectLocationContext) ->
+ // withContext(presenter.uiContext) {
+ // if (!locationModified) {
+ // val suggestedVirtualEnvPath = runCatching {
+ // suggestVirtualEnvPath(projectPath, projectLocationContext)
+ // }.getOrLogException(LOG)
+ // location.set(suggestedVirtualEnvPath.orEmpty())
+ // }
+ // }
+ // }
+ //}
}
override fun onShown() {
val modalityState = ModalityState.current().asContextElement()
- state.scope.launch(Dispatchers.EDT + modalityState) {
- val basePath = suggestVirtualEnvPath(state.projectPath.get(), presenter.projectLocationContext)
- location.set(basePath)
- }
- }
+ model.scope.launch(Dispatchers.EDT + modalityState) {
- private suspend fun suggestVirtualEnvPath(projectPath: String, projectLocationContext: ProjectLocationContext): String =
- projectLocationContext.suggestVirtualEnvPath(projectPath)
+ val suggestedVirtualEnvPath = model.suggestVenvPath()!! // todo nullability issue
+ model.state.venvPath.set(suggestedVirtualEnvPath)
+
+ //val projectBasePath = state.projectPath.get()
+
+ //val basePath = if (model is PythonLocalAddInterpreterModel)
+ // withContext(Dispatchers.IO) {
+ // FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(projectBasePath))
+ // }
+ //else {
+ // ""
+ // // todo fix for wsl and targets
+ // //val suggestedVirtualEnvName = PathUtil.getFileName(projectBasePath)
+ // //val userHome = presenter.projectLocationContext.fetchUserHomeDirectory()
+ // //userHome?.resolve(DEFAULT_VIRTUALENVS_DIR)?.resolve(suggestedVirtualEnvName)?.toString().orEmpty()
+ //}
+ //
+ //model.state.venvPath.set(basePath)
+ }
+
+ versionComboBox.setItems(model.baseInterpreters)
+
+
+ }
private fun suggestVenvName(currentName: String): String {
val digitSuffix = currentName.takeLastWhile { it.isDigit() }
@@ -170,7 +185,8 @@ class PythonNewVirtualenvCreator(presenter: PythonAddInterpreterPresenter) : Pyt
}
override fun getOrCreateSdk(): Sdk? {
- return presenter.setupVirtualenv(Path.of(location.get()), state.projectPath.get(), basePythonVersion.get()!!)
+ // todo remove project path, or move to controller
+ return model.setupVirtualenv((Path.of(model.state.venvPath.get())), model.projectPath.get(), model.state.baseInterpreter.get()!!)
}
companion object {
@@ -184,22 +200,6 @@ class PythonNewVirtualenvCreator(presenter: PythonAddInterpreterPresenter) : Pyt
*/
private const val DEFAULT_VIRTUALENVS_DIR = ".virtualenvs"
- private suspend fun ProjectLocationContext.suggestVirtualEnvPath(projectBasePath: String?): String =
- if (this is LocalContext)
- withContext(Dispatchers.IO) {
- FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(projectBasePath))
- }
- else suggestVirtualEnvPathGeneral(projectBasePath)
-
-
- /**
- * The simplest case of [PySdkSettings.getPreferredVirtualEnvBasePath] implemented.
- */
- private suspend fun ProjectLocationContext.suggestVirtualEnvPathGeneral(projectBasePath: String?): String {
- val suggestedVirtualEnvName = projectBasePath?.let { PathUtil.getFileName(it) } ?: "venv"
- val userHome = fetchUserHomeDirectory()
- return userHome?.resolve(DEFAULT_VIRTUALENVS_DIR)?.resolve(suggestedVirtualEnvName)?.toString().orEmpty()
- }
/**
* Checks if [this] field's text points to an existing file or directory. Calls [Path.exists] from EDT with care to avoid freezing the
@@ -238,13 +238,15 @@ class PythonNewVirtualenvCreator(presenter: PythonAddInterpreterPresenter) : Pyt
}
override fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo {
- val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ //val statisticsTarget = if (presenter.projectLocationContext is WslContext) InterpreterTarget.TARGET_WSL else target.toStatisticsField()
+ val statisticsTarget = target.toStatisticsField() // todo fix for wsl
return InterpreterStatisticsInfo(InterpreterType.VIRTUALENV,
statisticsTarget,
- inheritSitePackages.get(),
- makeAvailable.get(),
+ model.state.inheritSitePackages.get(),
+ model.state.makeAvailable.get(),
false,
- presenter.projectLocationContext is WslContext,
+ //presenter.projectLocationContext is WslContext,
+ false, // todo fix for wsl
InterpreterCreationMode.CUSTOM)
}
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkComboBoxWithBrowseButtonEditor.kt b/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkComboBoxWithBrowseButtonEditor.kt
index 6b439eced23e..5c7a3e618152 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkComboBoxWithBrowseButtonEditor.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/PythonSdkComboBoxWithBrowseButtonEditor.kt
@@ -1,8 +1,7 @@
-// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2024 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
import com.intellij.icons.AllIcons
-import com.intellij.openapi.fileChooser.FileChooser
import com.intellij.openapi.observable.util.addMouseHoverListener
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.ComboBox
@@ -13,13 +12,7 @@ import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.gridLayout.UnscaledGaps
import com.intellij.ui.hover.HoverListener
-import com.intellij.util.text.nullize
import com.jetbrains.python.PyBundle.message
-import com.jetbrains.python.sdk.PythonSdkType
-import kotlinx.coroutines.CoroutineStart
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import java.awt.Component
import java.awt.Cursor
import java.awt.event.ActionListener
@@ -30,8 +23,8 @@ import javax.swing.ComboBoxEditor
import javax.swing.JComponent
import javax.swing.JLabel
-class PythonSdkComboBoxWithBrowseButtonEditor(val comboBox: ComboBox,
- val presenter: PythonAddInterpreterPresenter,
+class PythonSdkComboBoxWithBrowseButtonEditor(val comboBox: ComboBox,
+ val controller: PythonAddInterpreterModel,
onPathSelected: (String) -> Unit) : ComboBoxEditor {
private val component = SimpleColoredComponent()
private val panel: JComponent
@@ -79,22 +72,32 @@ class PythonSdkComboBoxWithBrowseButtonEditor(val comboBox: ComboBox,
}
})
+ val browseAction = controller.createBrowseAction()
+
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent?) {
if (!isBusy) {
- val currentBaseSdkVirtualFile = (_item as? Sdk)?.let { sdk ->
- val currentBaseSdkPathOnTarget = sdk.homePath.nullize(nullizeSpaces = true)
- currentBaseSdkPathOnTarget?.let { presenter.tryGetVirtualFile(it) }
- }
- FileChooser.chooseFile(PythonSdkType.getInstance().homeChooserDescriptor,
- null,
- currentBaseSdkVirtualFile) { file ->
- val nioPath = file?.toNioPath() ?: return@chooseFile
- val targetPath = presenter.getPathOnTarget(nioPath)
- comboBox.setPathToSelectAfterModelUpdate(targetPath)
- onPathSelected(targetPath)
- }
+ // todo add interpreter to allSdks
+ browseAction()?.let { onPathSelected(it) }
+
+
+
+ //onPathSelected(selectedInterpreter)
+
+ //val currentBaseSdkVirtualFile = (_item as? Sdk)?.let { sdk ->
+ // val currentBaseSdkPathOnTarget = sdk.homePath.nullize(nullizeSpaces = true)
+ // currentBaseSdkPathOnTarget?.let { presenter.tryGetVirtualFile(it) }
+ //}
+ //
+ //FileChooser.chooseFile(PythonSdkType.getInstance().homeChooserDescriptor,
+ // null,
+ // currentBaseSdkVirtualFile) { file ->
+ // val nioPath = file?.toNioPath() ?: return@chooseFile
+ // val targetPath = presenter.getPathOnTarget(nioPath)
+ // comboBox.setPathToSelectAfterModelUpdate(targetPath)
+ // onPathSelected(targetPath)
+ //}
}
}
})
@@ -105,13 +108,6 @@ class PythonSdkComboBoxWithBrowseButtonEditor(val comboBox: ComboBox,
}
panel.border = null
- presenter.scope.launch(start = CoroutineStart.UNDISPATCHED) {
- presenter.detectingSdks.collectLatest {
- withContext(presenter.uiContext) {
- setBusy(it)
- }
- }
- }
}
@@ -119,14 +115,22 @@ class PythonSdkComboBoxWithBrowseButtonEditor(val comboBox: ComboBox,
if (_item == anObject) return
_item = anObject
component.clear()
- if (anObject is Sdk) component.customizeForPythonSdk(anObject)
+ if (anObject is PythonSelectableInterpreter) component.customizeForPythonInterpreter(anObject)
}
- private fun setBusy(busy: Boolean) {
+ fun setBusy(busy: Boolean) {
isBusy = busy
iconLabel.icon = if (isBusy) AnimatedIcon.Default.INSTANCE else AllIcons.General.OpenDisk
component.isEnabled = !isBusy
comboBox.isEnabled = !isBusy
+ if (busy) {
+ component.clear()
+ component.append("Loading interpeterers")
+ }
+ else {
+ component.clear()
+ if (item is PythonSelectableInterpreter) component.customizeForPythonInterpreter(item as PythonSelectableInterpreter)
+ }
}
override fun getEditorComponent(): Component = panel
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/common.kt b/python/src/com/jetbrains/python/sdk/add/v2/common.kt
index 574fc84b5318..a175fb0d17a7 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/common.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/common.kt
@@ -1,6 +1,7 @@
// Copyright 2000-2023 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
+import com.intellij.execution.target.TargetEnvironmentConfiguration
import com.intellij.icons.AllIcons
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationGroupManager
@@ -17,12 +18,9 @@ import com.intellij.ui.dsl.builder.Panel
import com.jetbrains.python.PyBundle.message
import com.jetbrains.python.icons.PythonIcons
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
-import com.jetbrains.python.sdk.LOGGER
-import com.jetbrains.python.sdk.PyDetectedSdk
-import com.jetbrains.python.sdk.installSdkIfNeeded
+import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.pipenv.PIPENV_ICON
import com.jetbrains.python.sdk.poetry.POETRY_ICON
-import com.jetbrains.python.sdk.setup
import com.jetbrains.python.statistics.InterpreterTarget
import kotlinx.coroutines.CoroutineScope
import javax.swing.Icon
@@ -38,12 +36,13 @@ interface PythonTargetEnvironmentInterpreterCreator {
fun createStatisticsInfo(): InterpreterStatisticsInfo = throw NotImplementedError()
}
-abstract class PythonAddEnvironment(val presenter: PythonAddInterpreterPresenter) {
- val state: PythonAddInterpreterState
- get() = presenter.state
+abstract class PythonAddEnvironment(open val model: PythonAddInterpreterModel) {
+
+ val state: AddInterpreterState
+ get() = model.state
internal val propertyGraph
- get() = state.propertyGraph
+ get() = model.propertyGraph
abstract fun buildOptions(panel: Panel, validationRequestor: DialogValidationRequestor)
open fun onShown() {}
@@ -51,6 +50,13 @@ abstract class PythonAddEnvironment(val presenter: PythonAddInterpreterPresenter
abstract fun createStatisticsInfo(target: PythonInterpreterCreationTargets): InterpreterStatisticsInfo
}
+abstract class PythonNewEnvironmentCreator(override val model: PythonMutableTargetAddInterpreterModel) : PythonAddEnvironment(model)
+abstract class PythonExistingEnvironmentConfigurator(model: PythonAddInterpreterModel) : PythonAddEnvironment(model)
+
+
+
+
+
enum class PythonSupportedEnvironmentManagers(val nameKey: String, val icon: Icon) {
VIRTUALENV("sdk.create.custom.virtualenv", PythonIcons.Python.Virtualenv),
CONDA("sdk.create.custom.conda", PythonIcons.Python.Anaconda),
@@ -111,4 +117,20 @@ internal fun setupSdkIfDetected(sdk: Sdk, existingSdks: List): Sdk = when (
newSdk
}
else -> sdk
+}
+
+
+
+internal fun setupSdkIfDetected(interpreter: PythonSelectableInterpreter, existingSdks: List, targetConfig: TargetEnvironmentConfiguration? = null): Sdk? {
+ if (interpreter is ExistingSelectableInterpreter) return interpreter.sdk
+
+ val homeDir = interpreter.homePath.virtualFileOnTarget(targetConfig) ?: return null // todo handle
+ val newSdk = SdkConfigurationUtil.setupSdk(existingSdks.toTypedArray(),
+ homeDir,
+ PythonSdkType.getInstance(),
+ false,
+ null, // todo create additional data for target
+ null) ?: return null
+ SdkConfigurationUtil.addSdk(newSdk)
+ return newSdk
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/condaUtils.kt b/python/src/com/jetbrains/python/sdk/add/v2/condaUtils.kt
index ff98cfffdb7a..daa5213ed98e 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/condaUtils.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/condaUtils.kt
@@ -25,23 +25,23 @@ import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
import com.jetbrains.python.sdk.flavors.conda.PyCondaFlavorData
import kotlinx.coroutines.Dispatchers
-internal val PythonAddInterpreterPresenter.condaExecutableOnTarget: String
- @RequiresEdt get() = state.condaExecutable.get().convertToPathOnTarget(targetEnvironmentConfiguration)
+@RequiresEdt
+internal fun PythonAddInterpreterModel.createCondaCommand(): PyCondaCommand =
+ PyCondaCommand(state.condaExecutable.get().convertToPathOnTarget(targetEnvironmentConfiguration),
+ targetConfig = targetEnvironmentConfiguration)
@RequiresEdt
-internal fun PythonAddInterpreterPresenter.createCondaCommand(): PyCondaCommand =
- PyCondaCommand(condaExecutableOnTarget, targetConfig = targetEnvironmentConfiguration)
-
-@RequiresEdt
-internal fun PythonAddInterpreterPresenter.createCondaEnvironment(request: NewCondaEnvRequest): Sdk? {
+internal fun PythonAddInterpreterModel.createCondaEnvironment(request: NewCondaEnvRequest): Sdk? {
+ val project = ProjectManager.getInstance().defaultProject
+ val existingSdks = this@createCondaEnvironment.existingSdks
val sdk = runWithModalProgressBlocking(ModalTaskOwner.guess(),
PyBundle.message("sdk.create.custom.conda.create.progress"),
TaskCancellation.nonCancellable()) {
createCondaCommand()
.createCondaSdkAlongWithNewEnv(request,
Dispatchers.EDT,
- state.allExistingSdks.get(),
- ProjectManager.getInstance().defaultProject).getOrNull()
+ existingSdks,
+ project).getOrNull()
} ?: return null
(sdk.sdkType as PythonSdkType).setupSdkPaths(sdk)
@@ -49,25 +49,25 @@ internal fun PythonAddInterpreterPresenter.createCondaEnvironment(request: NewCo
return sdk
}
-@RequiresEdt
-internal fun PythonAddInterpreterPresenter.selectCondaEnvironment(identity: PyCondaEnvIdentity): Sdk {
- val existingSdk = ProjectJdkTable.getInstance().findJdk(identity.userReadableName)
- if (existingSdk != null && isCondaSdk(existingSdk)) return existingSdk
+//@RequiresEdt
+//internal fun PythonAddInterpreterModel.selectCondaEnvironment(identity: PyCondaEnvIdentity): Sdk {
+// val existingSdk = ProjectJdkTable.getInstance().findJdk(identity.userReadableName)
+// if (existingSdk != null && isCondaSdk(existingSdk)) return existingSdk
+//
+// val sdk = runWithModalProgressBlocking(ModalTaskOwner.guess(),
+// PyBundle.message("sdk.create.custom.conda.create.progress"),
+// TaskCancellation.nonCancellable()) {
+// createCondaCommand().createCondaSdkFromExistingEnv(identity,
+// state.allExistingSdks.get(),
+// ProjectManager.getInstance().defaultProject)
+// }
+//
+// (sdk.sdkType as PythonSdkType).setupSdkPaths(sdk)
+// SdkConfigurationUtil.addSdk(sdk)
+// return sdk
+//}
- val sdk = runWithModalProgressBlocking(ModalTaskOwner.guess(),
- PyBundle.message("sdk.create.custom.conda.create.progress"),
- TaskCancellation.nonCancellable()) {
- createCondaCommand().createCondaSdkFromExistingEnv(identity,
- state.allExistingSdks.get(),
- ProjectManager.getInstance().defaultProject)
- }
-
- (sdk.sdkType as PythonSdkType).setupSdkPaths(sdk)
- SdkConfigurationUtil.addSdk(sdk)
- return sdk
-}
-
-private fun isCondaSdk(sdk: Sdk): Boolean = (sdk.sdkAdditionalData as? PythonSdkAdditionalData)?.flavorAndData?.data is PyCondaFlavorData
+internal fun isCondaSdk(sdk: Sdk): Boolean = (sdk.sdkAdditionalData as? PythonSdkAdditionalData)?.flavorAndData?.data is PyCondaFlavorData
@RequiresEdt
internal fun PythonAddInterpreterPresenter.createExecutor(): TargetCommandExecutor = targetEnvironmentConfiguration.toExecutor()
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/dialogs.kt b/python/src/com/jetbrains/python/sdk/add/v2/dialogs.kt
new file mode 100644
index 000000000000..29fd77ec3bb2
--- /dev/null
+++ b/python/src/com/jetbrains/python/sdk/add/v2/dialogs.kt
@@ -0,0 +1,78 @@
+// Copyright 2000-2024 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
+
+import com.intellij.openapi.application.EDT
+import com.intellij.openapi.application.ModalityState
+import com.intellij.openapi.application.asContextElement
+import com.intellij.openapi.components.service
+import com.intellij.openapi.observable.properties.AtomicProperty
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.projectRoots.ProjectJdkTable
+import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
+import com.intellij.openapi.ui.DialogWrapper
+import com.intellij.openapi.ui.validation.WHEN_PROPERTY_CHANGED
+import com.intellij.ui.AncestorListenerAdapter
+import com.intellij.ui.dsl.builder.panel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.awt.Dimension
+import javax.swing.BoxLayout
+import javax.swing.JComponent
+import javax.swing.JPanel
+import javax.swing.event.AncestorEvent
+
+
+class PythonAddLocalInterpreterDialog(project: Project) : DialogWrapper(project) {
+ val outerPanel = JPanel().apply {
+ layout = BoxLayout(this, BoxLayout.X_AXIS)
+ preferredSize = Dimension(500, 250) // todo scale dimensions
+ }
+
+ private lateinit var model: PythonLocalAddInterpreterModel
+ private lateinit var mainPanel: PythonAddCustomInterpreter
+
+ init {
+ title = "Add Python Interpreter"
+ isResizable = true
+ init()
+
+ outerPanel.addAncestorListener(object : AncestorListenerAdapter() {
+ override fun ancestorAdded(event: AncestorEvent?) {
+ val basePath = project.basePath!!
+ model = PythonLocalAddInterpreterModel(service().coroutineScope,
+ Dispatchers.EDT + ModalityState.current().asContextElement(), AtomicProperty(basePath))
+ model.navigator.selectionMode = AtomicProperty(PythonInterpreterSelectionMode.CUSTOM)
+ mainPanel = PythonAddCustomInterpreter(model)
+
+ val dialogPanel = panel {
+ mainPanel.buildPanel(this, WHEN_PROPERTY_CHANGED(AtomicProperty(basePath)))
+ }
+
+ dialogPanel.registerValidators(myDisposable) { validations ->
+ val anyErrors = validations.entries.any { (key, value) -> key.isVisible && !value.okEnabled }
+ isOKActionEnabled = !anyErrors
+ }
+
+ outerPanel.add(dialogPanel)
+
+ model.scope.launch(Dispatchers.EDT + ModalityState.current().asContextElement()) {
+ model.initialize()
+ mainPanel.onShown()
+ }
+ }
+ })
+
+ }
+
+
+ override fun doOKAction() {
+ super.doOKAction()
+ val sdk = mainPanel.getSdk()
+ if (sdk != null) {
+ val existing = ProjectJdkTable.getInstance().findJdk(sdk.name)
+ SdkConfigurationUtil.addSdk(sdk)
+ }
+ }
+
+ override fun createCenterPanel(): JComponent = outerPanel
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/models.kt b/python/src/com/jetbrains/python/sdk/add/v2/models.kt
new file mode 100644
index 000000000000..300777791461
--- /dev/null
+++ b/python/src/com/jetbrains/python/sdk/add/v2/models.kt
@@ -0,0 +1,312 @@
+// Copyright 2000-2024 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
+
+import com.intellij.execution.target.TargetEnvironmentConfiguration
+import com.intellij.ide.util.PropertiesComponent
+import com.intellij.openapi.application.EDT
+import com.intellij.openapi.application.ModalityState
+import com.intellij.openapi.application.asContextElement
+import com.intellij.openapi.diagnostic.getOrLogException
+import com.intellij.openapi.fileChooser.FileChooser
+import com.intellij.openapi.observable.properties.ObservableMutableProperty
+import com.intellij.openapi.observable.properties.ObservableProperty
+import com.intellij.openapi.observable.properties.PropertyGraph
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.util.io.FileUtil
+import com.jetbrains.python.configuration.PyConfigurableInterpreterList
+import com.jetbrains.python.newProject.steps.ProjectSpecificSettingsStep
+import com.jetbrains.python.psi.LanguageLevel
+import com.jetbrains.python.sdk.*
+import com.jetbrains.python.sdk.add.ProjectLocationContexts
+import com.jetbrains.python.sdk.add.target.conda.suggestCondaPath
+import com.jetbrains.python.sdk.add.v2.PythonAddInterpreterPresenter.ProjectPathWithContext
+import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
+import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
+import com.jetbrains.python.sdk.interpreters.detectSystemInterpreters
+import com.jetbrains.python.sdk.pipenv.pipEnvPath
+import com.jetbrains.python.sdk.poetry.poetryPath
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+import java.nio.file.Path
+import kotlin.coroutines.CoroutineContext
+
+
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class PythonAddInterpreterModel(val scope: CoroutineScope, val uiContext: CoroutineContext, projectPathProperty: ObservableProperty? = null) {
+
+ val propertyGraph = PropertyGraph()
+ val navigator = PythonNewEnvironmentDialogNavigator()
+ open val state = AddInterpreterState(propertyGraph)
+ open val targetEnvironmentConfiguration: TargetEnvironmentConfiguration? = null
+
+ val projectPath = projectPathProperty ?: propertyGraph.property("") // todo how to populate?
+
+ internal val knownInterpreters: MutableStateFlow> = MutableStateFlow(emptyList())
+ internal val detectedInterpreters: MutableStateFlow> = MutableStateFlow(emptyList())
+ val manuallyAddedInterpreters: MutableStateFlow> = MutableStateFlow(emptyList())
+ private var installable: List = emptyList()
+ val condaEnvironments: MutableStateFlow> = MutableStateFlow(emptyList())
+
+ var allInterpreters: StateFlow> = combine(knownInterpreters,
+ detectedInterpreters,
+ manuallyAddedInterpreters) { known, detected, added ->
+ added + known + detected
+ }
+ .stateIn(scope + uiContext, started = SharingStarted.Eagerly, initialValue = emptyList())
+
+
+ val baseInterpreters: StateFlow> = allInterpreters
+ .mapLatest {
+ it.filter { it !is ExistingSelectableInterpreter || it.isSystemWide } + installable
+ }
+ .stateIn(scope + uiContext, started = SharingStarted.Eagerly, initialValue = emptyList())
+
+
+ val interpreterLoading = MutableStateFlow(false)
+ val condaEnvironmentsLoading = MutableStateFlow(false)
+
+ open fun createBrowseAction(): () -> String? = TODO()
+
+ open suspend fun initialize() {
+ interpreterLoading.value = true
+ initInterpreterList()
+ detectCondaExecutable()
+ condaEnvironmentsLoading.value = true
+ detectCondaEnvironments()
+ condaEnvironmentsLoading.value = false
+ interpreterLoading.value = false
+ }
+
+
+ suspend fun detectCondaExecutable() {
+ withContext(Dispatchers.IO) {
+ val executor = targetEnvironmentConfiguration.toExecutor()
+ val suggestedCondaPath = runCatching {
+ suggestCondaPath(targetCommandExecutor = executor)
+ }.getOrLogException(PythonAddInterpreterPresenter.LOG)
+ val suggestedCondaLocalPath = suggestedCondaPath?.toLocalPathOn(targetEnvironmentConfiguration)
+ withContext(uiContext) {
+ state.condaExecutable.set(suggestedCondaLocalPath?.toString().orEmpty())
+ }
+
+ //val environments = suggestedCondaPath?.let { PyCondaEnv.getEnvs(executor, suggestedCondaPath).getOrLogException(
+ // PythonAddInterpreterPresenter.LOG) }
+ //baseConda = environments?.find { env -> env.envIdentity.let { it is PyCondaEnvIdentity.UnnamedEnv && it.isBase } }
+
+ }
+ }
+ suspend fun detectCondaEnvironments() {
+ withContext(Dispatchers.IO) {
+ val commandExecutor = targetEnvironmentConfiguration.toExecutor()
+ val environments = PyCondaEnv.getEnvs(commandExecutor, state.condaExecutable.get()).getOrLogException(LOG) ?: emptyList()
+ val baseConda = environments.find { env -> env.envIdentity.let { it is PyCondaEnvIdentity.UnnamedEnv && it.isBase } }
+
+ withContext(uiContext) {
+ condaEnvironments.value = environments
+ state.baseCondaEnv.set(baseConda)
+ }
+ }
+ }
+
+
+
+ suspend fun initInterpreterList() {
+ withContext(Dispatchers.IO) {
+ val existingSdks = PyConfigurableInterpreterList.getInstance(null).getModel().sdks.toList()
+ val allValidSdks = ProjectSpecificSettingsStep.getValidPythonSdks(existingSdks)
+ .map { ExistingSelectableInterpreter(it, PySdkUtil.getLanguageLevelForSdk(it), it.isSystemWide) }
+
+ val languageLevels = allValidSdks.mapTo(HashSet()) { it.languageLevel } // todo add detected here
+ val filteredInstallable = getSdksToInstall()
+ .map { LanguageLevel.fromPythonVersion(it.versionString) to it }
+ .filter { it.first !in languageLevels }
+ .sortedByDescending { it.first }
+ .map { InstallableSelectableInterpreter(it.second) }
+
+ val detected = runCatching {
+ detectSystemInterpreters(projectDir = null, null, targetEnvironmentConfiguration, existingSdks)
+ // todo check fix about appending recently used
+ //return@runCatching appendMostRecentlyUsedBaseSdk(detected, context.targetEnvironmentConfiguration)
+ //detected.filter { it.homePath !in savedPaths }
+ }.getOrLogException(PythonAddInterpreterPresenter.LOG) ?: emptyList()
+
+ withContext(uiContext) {
+ installable = filteredInstallable
+ knownInterpreters.value = allValidSdks // todo check target?
+ detectedInterpreters.value = detected
+ }
+
+ }
+ }
+
+ open fun addInterpreter(path: String): PythonSelectableInterpreter = TODO()
+
+ open fun suggestVenvPath(): String? = ""
+}
+
+
+abstract class PythonMutableTargetAddInterpreterModel(scope: CoroutineScope, uiContext: CoroutineContext, projectPathProperty: ObservableProperty? = null)
+ : PythonAddInterpreterModel(scope, uiContext, projectPathProperty) {
+ override val state: MutableTargetState = MutableTargetState(propertyGraph)
+
+ override suspend fun initialize() {
+ super.initialize()
+ detectPoetryExecutable()
+ detectPipEnvExecutable()
+ }
+
+ suspend fun detectPoetryExecutable() {
+ // todo this is local case, fix for targets
+ val savedPath = PropertiesComponent.getInstance().poetryPath
+ if (savedPath != null) {
+ state.poetryExecutable.set(savedPath)
+ }
+ else {
+ val modalityState = ModalityState.current().asContextElement()
+ scope.launch(Dispatchers.IO) {
+ val poetryExecutable = com.jetbrains.python.sdk.poetry.detectPoetryExecutable()
+ withContext(Dispatchers.EDT + modalityState) {
+ poetryExecutable?.let { state.poetryExecutable.set(it.path) }
+ }
+ }
+ }
+ }
+
+ suspend fun detectPipEnvExecutable() {
+ // todo this is local case, fix for targets
+ val savedPath = PropertiesComponent.getInstance().pipEnvPath
+ if (savedPath != null) {
+ state.pipenvExecutable.set(savedPath)
+ }
+ else {
+ val modalityState = ModalityState.current().asContextElement()
+ scope.launch(Dispatchers.IO) {
+ val detectedExecutable = com.jetbrains.python.sdk.pipenv.detectPipEnvExecutable()
+ withContext(Dispatchers.EDT + modalityState) {
+ detectedExecutable?.let { state.pipenvExecutable.set(it.path) }
+ }
+ }
+ }
+ }
+}
+
+
+open class PythonLocalAddInterpreterModel(scope: CoroutineScope, uiContext: CoroutineContext, projectPathProperty: ObservableProperty? = null)
+ : PythonMutableTargetAddInterpreterModel(scope, uiContext, projectPathProperty) {
+
+ override suspend fun initialize() {
+ super.initialize()
+
+ val mostRecentlyUsedBasePath = PySdkSettings.instance.preferredVirtualEnvBaseSdk
+ val interpreterToSelect = detectedInterpreters.value.find { it.homePath == mostRecentlyUsedBasePath }
+ ?: baseInterpreters.value
+ .filterIsInstance()
+ .maxByOrNull { it.languageLevel }
+
+ if (interpreterToSelect != null) {
+ state.baseInterpreter.set(interpreterToSelect)
+ }
+ }
+
+ override fun createBrowseAction(): () -> String? {
+ return {
+ var path: Path? = null
+ FileChooser.chooseFile(PythonSdkType.getInstance().homeChooserDescriptor, null, null) { file ->
+ path = file?.toNioPath()
+ }
+ path?.toString()
+ }
+ }
+
+ override fun addInterpreter(path: String): PythonSelectableInterpreter {
+ val interpreter = ManuallyAddedSelectableInterpreter(path)
+ manuallyAddedInterpreters.value += interpreter
+ return interpreter
+ }
+
+ override fun suggestVenvPath(): String? {
+ // todo should this be a coroutine?
+ return FileUtil.toSystemDependentName(PySdkSettings.instance.getPreferredVirtualEnvBasePath(projectPath.get()))
+ }
+}
+
+class PythonWslCapableLocalAddInterpreterModel(scope: CoroutineScope, uiContext: CoroutineContext) : PythonMutableTargetAddInterpreterModel(scope, uiContext) {
+
+ private val projectLocationContexts = ProjectLocationContexts()
+ private val _projectWithContextFlow: MutableStateFlow =
+ MutableStateFlow(projectPath.get().associateWithContext())
+
+ init {
+ projectPath.afterChange { projectPath ->
+ val context = projectLocationContexts.getProjectLocationContextFor(projectPath)
+ _projectWithContextFlow.value = ProjectPathWithContext(projectPath, context)
+ }
+ }
+
+ private fun String.associateWithContext(): ProjectPathWithContext =
+ ProjectPathWithContext(projectPath = this, projectLocationContexts.getProjectLocationContextFor(projectPath = this))
+}
+
+class PythonSshAddInterpreterModel(scope: CoroutineScope, uiContext: CoroutineContext) : PythonMutableTargetAddInterpreterModel(scope, uiContext)
+
+class PythonDockerAddInterpreterModel(scope: CoroutineScope, uiContext: CoroutineContext) : PythonAddInterpreterModel(scope, uiContext)
+
+
+
+// todo does it need target configuration
+abstract class PythonSelectableInterpreter {
+ abstract val homePath: String
+}
+
+class ExistingSelectableInterpreter(val sdk: Sdk, val languageLevel: LanguageLevel, val isSystemWide: Boolean) : PythonSelectableInterpreter() {
+ override val homePath = sdk.homePath!! // todo is it safe
+}
+
+
+class DetectedSelectableInterpreter(override val homePath: String, targetConfiguration: TargetEnvironmentConfiguration? = null) : PythonSelectableInterpreter()
+
+class ManuallyAddedSelectableInterpreter(override val homePath: String) : PythonSelectableInterpreter()
+class InstallableSelectableInterpreter(val sdk: PySdkToInstall) : PythonSelectableInterpreter() {
+ override val homePath: String = ""
+}
+
+
+
+
+class InterpreterSeparator(val text: String) : PythonSelectableInterpreter() {
+ override val homePath: String = ""
+}
+
+
+
+
+
+
+open class AddInterpreterState(propertyGraph: PropertyGraph) {
+ val selectedInterpreter: ObservableMutableProperty = propertyGraph.property(null)
+ val condaExecutable: ObservableMutableProperty = propertyGraph.property("")
+ val selectedCondaEnv: ObservableMutableProperty = propertyGraph.property(null)
+ val baseCondaEnv: ObservableMutableProperty = propertyGraph.property(null)
+}
+
+class MutableTargetState(propertyGraph: PropertyGraph) : AddInterpreterState(propertyGraph) {
+ val baseInterpreter: ObservableMutableProperty = propertyGraph.property(null)
+ val newCondaEnvName: ObservableMutableProperty = propertyGraph.property("")
+ val poetryExecutable: ObservableMutableProperty = propertyGraph.property("")
+ val pipenvExecutable: ObservableMutableProperty = propertyGraph.property("")
+ val venvPath: ObservableMutableProperty = propertyGraph.property("")
+ val inheritSitePackages = propertyGraph.property(false)
+ val makeAvailable = propertyGraph.property(false)
+}
+
+
+
+val PythonAddInterpreterModel.existingSdks
+ get() = allInterpreters.value.filterIsInstance().map { it.sdk }
+
+val PythonAddInterpreterModel.baseSdks
+ get() = baseInterpreters.value.filterIsInstance().map { it.sdk }
+
+fun PythonAddInterpreterModel.findInterpreter(path: String): PythonSelectableInterpreter? {
+ return allInterpreters.value.asSequence().find { it.homePath == path}
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/presentersExt.kt b/python/src/com/jetbrains/python/sdk/add/v2/presentersExt.kt
new file mode 100644
index 000000000000..a621d2066abf
--- /dev/null
+++ b/python/src/com/jetbrains/python/sdk/add/v2/presentersExt.kt
@@ -0,0 +1,107 @@
+// Copyright 2000-2024 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
+
+import com.intellij.execution.ExecutionException
+import com.intellij.openapi.module.ModuleUtil
+import com.intellij.openapi.progress.ProgressManager
+import com.intellij.openapi.project.ProjectManager
+import com.intellij.openapi.projectRoots.ProjectJdkTable
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
+import com.intellij.openapi.vfs.StandardFileSystems
+import com.intellij.platform.ide.progress.ModalTaskOwner
+import com.intellij.platform.ide.progress.TaskCancellation
+import com.intellij.platform.ide.progress.runWithModalProgressBlocking
+import com.jetbrains.python.PyBundle
+import com.jetbrains.python.sdk.*
+import com.jetbrains.python.sdk.add.target.conda.createCondaSdkFromExistingEnv
+import com.jetbrains.python.sdk.configuration.createVirtualEnvSynchronously
+import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand
+import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
+import com.jetbrains.python.sdk.suggestAssociatedSdkName
+import java.nio.file.Path
+
+
+// todo should it be overriden for targets?
+internal fun PythonMutableTargetAddInterpreterModel.setupVirtualenv(venvPath: Path, projectPath: String, baseSdk: PythonSelectableInterpreter): Sdk? {
+
+ val venvPathOnTarget = venvPath.convertToPathOnTarget(targetEnvironmentConfiguration)
+
+ val baseSdkPath = when (baseSdk) {
+ is InstallableSelectableInterpreter -> installBaseSdk(baseSdk.sdk, this.existingSdks)?.homePath // todo handle errors
+ is ExistingSelectableInterpreter -> baseSdk.sdk.homePath
+ is DetectedSelectableInterpreter, is ManuallyAddedSelectableInterpreter -> baseSdk.homePath
+ else -> error("Unknown interpreter")
+ }
+
+
+ createVirtualenv(baseSdkPath!!, // todo handle null
+ venvPathOnTarget,
+ projectPath,
+ targetEnvironmentConfiguration,
+ this.existingSdks,
+ null,
+ null,
+ inheritSitePackages = state.inheritSitePackages.get(),
+ makeShared = state.makeAvailable.get())
+
+ if (targetEnvironmentConfiguration == null) {
+ val venvPython = PythonSdkUtil.getPythonExecutable(venvPathOnTarget)
+
+ val homeFile = try {
+ StandardFileSystems.local().refreshAndFindFileByPath(venvPython!!)!!
+ }
+ catch (e: ExecutionException) {
+ showSdkExecutionException(null, e, PyBundle.message("python.sdk.failed.to.create.interpreter.title"))
+ return null
+ }
+
+ val suggestedName = /*suggestedSdkName ?:*/ suggestAssociatedSdkName(homeFile.path, projectPath)
+ val newSdk = SdkConfigurationUtil.setupSdk(existingSdks.toTypedArray(), homeFile,
+ PythonSdkType.getInstance(),
+ false, null, suggestedName)
+
+ SdkConfigurationUtil.addSdk(newSdk!!)
+
+ // todo check exclude
+ ProjectManager.getInstance().openProjects
+ .firstNotNullOfOrNull { ModuleUtil.findModuleForFile(homeFile, it) }
+ ?.excludeInnerVirtualEnv(newSdk)
+
+
+ return newSdk
+
+ }
+ // todo find venv path on target
+
+ return null
+}
+
+
+
+// todo rewrite this
+internal fun PythonAddInterpreterModel.selectCondaEnvironment(identity: PyCondaEnvIdentity): Sdk {
+ val existingSdk = ProjectJdkTable.getInstance().findJdk(identity.userReadableName)
+ if (existingSdk != null && isCondaSdk(existingSdk)) return existingSdk
+
+ val sdk = runWithModalProgressBlocking(ModalTaskOwner.guess(),
+ PyBundle.message("sdk.create.custom.conda.create.progress"),
+ TaskCancellation.nonCancellable()) {
+ //PyCondaCommand(condaExecutableOnTarget, targetConfig = targetEnvironmentConfiguration)
+ PyCondaCommand(state.condaExecutable.get(), targetConfig = targetEnvironmentConfiguration).createCondaSdkFromExistingEnv(identity,
+ this@selectCondaEnvironment.existingSdks,
+ ProjectManager.getInstance().defaultProject)
+ }
+
+ (sdk.sdkType as PythonSdkType).setupSdkPaths(sdk)
+ SdkConfigurationUtil.addSdk(sdk)
+ return sdk
+}
+
+
+internal fun PythonAddInterpreterModel.installPythonIfNeeded(interpreter: PythonSelectableInterpreter): String? {
+ // todo use target config
+ return if (interpreter is InstallableSelectableInterpreter) {
+ installBaseSdk(interpreter.sdk, existingSdks)?.homePath ?: return null
+ } else interpreter.homePath
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/targetUtils.kt b/python/src/com/jetbrains/python/sdk/add/v2/targetUtils.kt
index 72876628d4a7..d42f84379078 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/targetUtils.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/targetUtils.kt
@@ -3,6 +3,8 @@ package com.jetbrains.python.sdk.add.v2
import com.intellij.execution.target.FullPathOnTarget
import com.intellij.execution.target.TargetEnvironmentConfiguration
+import com.intellij.openapi.vfs.StandardFileSystems
+import com.intellij.openapi.vfs.VirtualFile
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
import java.nio.file.Path
@@ -16,4 +18,11 @@ internal fun Path.convertToPathOnTarget(target: TargetEnvironmentConfiguration?)
internal fun FullPathOnTarget.toLocalPathOn(target: TargetEnvironmentConfiguration?): Path {
val mapper = target?.let { PythonInterpreterTargetEnvironmentFactory.getTargetWithMappedLocalVfs(it) }
return mapper?.getLocalPath(this) ?: Path.of(this)
+}
+
+internal fun String.virtualFileOnTarget(target: TargetEnvironmentConfiguration? = null): VirtualFile? {
+ if (target == null) return StandardFileSystems.local().findFileByPath(this)
+ val path = Path.of(this)
+ val mapper = PythonInterpreterTargetEnvironmentFactory.getTargetWithMappedLocalVfs(target) ?: return null
+ return mapper.getVfsFromTargetPath(mapper.getTargetPath(path)!!)
}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/uiUtils.kt b/python/src/com/jetbrains/python/sdk/add/v2/uiUtils.kt
index defcad93c289..fb712215ed35 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/uiUtils.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/uiUtils.kt
@@ -21,14 +21,12 @@ import com.intellij.openapi.ui.validation.and
import com.intellij.openapi.util.IconLoader
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.NlsSafe
-import com.intellij.ui.AnimatedIcon
-import com.intellij.ui.ColoredListCellRenderer
-import com.intellij.ui.SimpleColoredComponent
-import com.intellij.ui.SimpleTextAttributes
+import com.intellij.ui.*
import com.intellij.ui.components.ActionLink
import com.intellij.ui.components.fields.ExtendableTextComponent
import com.intellij.ui.components.fields.ExtendableTextField
import com.intellij.ui.dsl.builder.*
+import com.intellij.ui.dsl.builder.Cell
import com.intellij.ui.dsl.builder.components.ValidationType
import com.intellij.ui.dsl.builder.components.validationTooltip
import com.intellij.ui.util.preferredHeight
@@ -53,6 +51,7 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.Nls
+import java.awt.Component
import java.nio.file.Paths
import javax.swing.JList
import javax.swing.JPanel
@@ -67,7 +66,7 @@ internal fun PropertyGraph.booleanProperty(dependency: ObservableProperty
lazyProperty { dependency.get() == value }.apply { dependsOn(dependency) { dependency.get() == value } }
class PythonNewEnvironmentDialogNavigator {
- lateinit var selectionMode: ObservableMutableProperty
+ var selectionMode: ObservableMutableProperty? = null
lateinit var selectionMethod: ObservableMutableProperty
lateinit var newEnvManager: ObservableMutableProperty
lateinit var existingEnvManager: ObservableMutableProperty
@@ -75,7 +74,7 @@ class PythonNewEnvironmentDialogNavigator {
fun navigateTo(newMode: PythonInterpreterSelectionMode? = null,
newMethod: PythonInterpreterSelectionMethod? = null,
newManager: PythonSupportedEnvironmentManagers? = null) {
- newMode?.let { selectionMode.set(it) }
+ newMode?.let { selectionMode?.set(it) }
newMethod?.let { method ->
selectionMethod.set(method)
}
@@ -88,11 +87,16 @@ class PythonNewEnvironmentDialogNavigator {
}
}
+ // todo think about whether i need to save state in regular dialog
fun saveLastState() {
val properties = PropertiesComponent.getInstance()
- val mode = selectionMode.get()
- properties.setValue(FAV_MODE, mode.toString())
+ val mode = selectionMode?.let {
+ val mode = selectionMode!!.get()
+ properties.setValue(FAV_MODE, it.get().toString())
+ mode
+ } ?: VIRTUALENV
+
if (mode == CUSTOM) {
val method = selectionMethod.get()
val manager = if (method == CREATE_NEW) newEnvManager.get() else existingEnvManager.get()
@@ -116,7 +120,7 @@ class PythonNewEnvironmentDialogNavigator {
val modeString = properties.getValue(FAV_MODE) ?: return
val mode = PythonInterpreterSelectionMode.valueOf(modeString)
if (mode !in onlyAllowedSelectionModes) return
- selectionMode.set(mode)
+ selectionMode?.set(mode)
if (mode == CUSTOM) {
val method = PythonInterpreterSelectionMethod.valueOf(properties.getValue(FAV_METHOD) ?: return)
@@ -134,31 +138,39 @@ class PythonNewEnvironmentDialogNavigator {
}
}
-internal fun SimpleColoredComponent.customizeForPythonSdk(sdk: Sdk) {
- when (sdk) {
- is PyDetectedSdk -> {
+
+internal fun SimpleColoredComponent.customizeForPythonInterpreter(interpreter: PythonSelectableInterpreter) {
+ when (interpreter) {
+ is DetectedSelectableInterpreter, is ManuallyAddedSelectableInterpreter -> {
icon = IconLoader.getTransparentIcon(PythonPsiApiIcons.Python)
- append(sdk.homePath!!)
+ append(interpreter.homePath)
append(" " + message("sdk.rendering.detected.grey.text"), SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES)
}
- is PySdkToInstall -> {
+ is InstallableSelectableInterpreter -> {
icon = AllIcons.Actions.Download
- append(sdk.name)
+ append(interpreter.sdk.name)
append(" " + message("sdk.rendering.installable.grey.text"), SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES)
}
- else -> {
+ is ExistingSelectableInterpreter -> {
icon = PythonPsiApiIcons.Python
- append(sdk.versionString!!)
- append(" " + sdk.homePath!!, SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES)
+ append(interpreter.sdk.versionString!!)
+ append(" " + interpreter.homePath, SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES)
}
+ is InterpreterSeparator -> return
+ else -> error("Unknown PythonSelectableInterpreter type")
}
}
class PythonSdkComboBoxListCellRenderer : ColoredListCellRenderer() {
+
+ override fun getListCellRendererComponent(list: JList?, value: Any?, index: Int, selected: Boolean, hasFocus: Boolean): Component {
+ if (value is InterpreterSeparator) return TitledSeparator(value.text).apply { setLabelFocusable(false) }
+ return super.getListCellRendererComponent(list, value, index, selected, hasFocus)
+ }
+
override fun customizeCellRenderer(list: JList, value: Any?, index: Int, selected: Boolean, hasFocus: Boolean) {
- if (value !is Sdk) error("Not an Sdk")
- customizeForPythonSdk(value)
+ if (value is PythonSelectableInterpreter) customizeForPythonInterpreter(value)
}
}
@@ -190,30 +202,97 @@ class PythonEnvironmentComboBoxRenderer : ColoredListCellRenderer() {
}
}
-internal fun Row.pythonInterpreterComboBox(selectedSdkProperty: ObservableMutableProperty,
- presenter: PythonAddInterpreterPresenter,
- sdksFlow: StateFlow>,
- onPathSelected: (String) -> Unit): Cell> =
- comboBox(emptyList(), PythonSdkComboBoxListCellRenderer())
+internal fun Row.pythonInterpreterComboBox(selectedSdkProperty: ObservableMutableProperty, // todo not sdk
+ model: PythonAddInterpreterModel,
+ onPathSelected: (String) -> Unit, busyState: StateFlow? = null): Cell {
+
+ val comboBox = PythonInterpreterComboBox(selectedSdkProperty, model, onPathSelected)
+ val cell = cell(comboBox)
.bindItem(selectedSdkProperty)
.applyToComponent {
preferredHeight = 30
isEditable = true
- editor = PythonSdkComboBoxWithBrowseButtonEditor(this, presenter, onPathSelected)
+ }
- presenter.scope.launch(start = CoroutineStart.UNDISPATCHED) {
- sdksFlow.collectLatest { sdks ->
- withContext(presenter.uiContext) {
- removeAllItems()
- sdks.forEach(this@applyToComponent::addItem)
-
- val pathToSelect = tryGetAndRemovePathToSelectAfterModelUpdate() as? String
- val newValue = if (pathToSelect != null) sdks.find { it.homePath == pathToSelect } else findPrioritySdk(sdks)
- selectedSdkProperty.set(newValue)
- }
+ model.scope.launch(model.uiContext, start = CoroutineStart.UNDISPATCHED) {
+ busyState?.collectLatest { currentValue ->
+ withContext(model.uiContext) {
+ comboBox.setBusy(currentValue)
+ if (currentValue) {
+ // todo disable cell
}
}
}
+ }
+ return cell
+
+
+}
+
+class PythonInterpreterComboBox(val backingProperty: ObservableMutableProperty,
+ val controller: PythonAddInterpreterModel,
+ val onPathSelected: (String) -> Unit) : ComboBox() {
+
+ private lateinit var itemsFlow: StateFlow>
+ val items: List
+ get() = itemsFlow.value
+
+ private val interpreterToSelect = controller.propertyGraph.property(null)
+
+ init {
+ renderer = PythonSdkComboBoxListCellRenderer()
+ val newOnPathSelected: (String) -> Unit = {
+ interpreterToSelect.set(it)
+ onPathSelected(it)
+ }
+ editor = PythonSdkComboBoxWithBrowseButtonEditor(this, controller, newOnPathSelected)
+
+ }
+
+ fun setItems(flow: StateFlow>) {
+ itemsFlow = flow
+ controller.scope.launch(start = CoroutineStart.UNDISPATCHED) {
+ flow.collectLatest { interpreters ->
+ withContext(controller.uiContext) {
+ with(this@PythonInterpreterComboBox) {
+ val currentlySelected = selectedItem as PythonSelectableInterpreter?
+ removeAllItems()
+ interpreters.forEach(this::addItem)
+
+ val newPath = interpreterToSelect.get()
+ val newValue = if (newPath != null) {
+ val newItem = interpreters.find { it.homePath == newPath }
+ if (newItem == null) error("path but no item")
+ interpreterToSelect.set(null)
+ newItem
+ }
+ else if (currentlySelected == null || currentlySelected !in interpreters) {
+ interpreters.firstOrNull() // todo is there better fallback value?
+ }
+ else {
+ currentlySelected
+ }
+
+
+ //val newValue = if (newPath != null) {
+ // val newItem = interpreters.find { it.homePath == newPath }
+ // newPath = null
+ // newItem ?: currentlySelected
+ //} else currentlySelected
+
+
+ backingProperty.set(newValue) // todo do I even need to set it?
+ }
+
+ }
+ }
+ }
+ }
+
+ fun setBusy(busy: Boolean) {
+ (editor as PythonSdkComboBoxWithBrowseButtonEditor).setBusy(busy)
+ }
+}
private fun findPrioritySdk(sdkList: List): Sdk? {
val preferredSdkPath = PySdkSettings.instance.preferredVirtualEnvBaseSdk
@@ -374,12 +453,15 @@ fun Panel.executableSelector(executable: ObservableMutableProperty,
return textFieldCell!!
}
-internal fun createInstallCondaFix(presenter: PythonAddInterpreterPresenter): ActionLink {
+internal fun createInstallCondaFix(model: PythonAddInterpreterModel): ActionLink {
return ActionLink(message("sdk.create.conda.install.fix")) {
PythonSdkFlavor.clearExecutablesCache()
CondaInstallManager.installLatest(null)
- presenter.scope.launch(presenter.uiContext) {
- presenter.reloadConda(presenter.projectLocationContext)
+ model.scope.launch(model.uiContext) {
+ model.condaEnvironmentsLoading.value = true
+ model.detectCondaExecutable()
+ model.detectCondaEnvironments()
+ model.condaEnvironmentsLoading.value = false
}
}
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/virtualenv.kt b/python/src/com/jetbrains/python/sdk/add/v2/virtualenv.kt
new file mode 100644
index 000000000000..132e60cb8e1a
--- /dev/null
+++ b/python/src/com/jetbrains/python/sdk/add/v2/virtualenv.kt
@@ -0,0 +1,92 @@
+// Copyright 2000-2024 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
+
+import com.intellij.execution.process.CapturingProcessHandler
+import com.intellij.execution.process.ColoredProcessHandler
+import com.intellij.execution.target.TargetEnvironmentConfiguration
+import com.intellij.execution.target.TargetProgressIndicator
+import com.intellij.execution.target.TargetedCommandLineBuilder
+import com.intellij.execution.target.value.TargetValue
+import com.intellij.openapi.actionSystem.AnAction
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.progress.blockingContext
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.platform.ide.progress.ModalTaskOwner
+import com.intellij.platform.ide.progress.runWithModalProgressBlocking
+import com.jetbrains.python.PythonHelper
+import com.jetbrains.python.run.*
+import com.jetbrains.python.run.target.HelpersAwareLocalTargetEnvironmentRequest
+import com.jetbrains.python.sdk.PySdkSettings
+import com.jetbrains.python.sdk.add.target.TargetPanelExtension
+import com.jetbrains.python.sdk.configureBuilderToRunPythonOnTarget
+
+
+fun createVirtualenv(baseInterpreterPath: String,
+ venvRoot: String,
+ projectBasePath: String?,
+ targetConfiguration: TargetEnvironmentConfiguration?,
+ existingSdks: List,
+ project: Project?,
+ module: Module?,
+ inheritSitePackages: Boolean = false,
+ makeShared: Boolean = false,
+ targetPanelExtension: TargetPanelExtension? = null) {
+
+ // todo find request for targets (need sdk, maybe can work around it )
+ //PythonInterpreterTargetEnvironmentFactory.findPythonTargetInterpreter()
+ val request = HelpersAwareLocalTargetEnvironmentRequest()
+ //val targetRequest = request.targetEnvironmentRequest
+
+ val execution = prepareHelperScriptExecution(PythonHelper.VIRTUALENV_ZIPAPP, request) // todo what about legacy pythons?
+ if (inheritSitePackages) {
+ execution.addParameter("--system-site-packages")
+ }
+
+ execution.addParameter(venvRoot)
+ request.preparePyCharmHelpers()
+
+ val targetEnvironment = request.targetEnvironmentRequest.prepareEnvironment(TargetProgressIndicator.EMPTY)
+
+ //val targetedCommandLine = execution.buildTargetedCommandLine(targetEnvironment, sdk = null, emptyList())
+
+
+ val commandLineBuilder = TargetedCommandLineBuilder(targetEnvironment.request)
+ commandLineBuilder.setWorkingDirectory(projectBasePath!!)
+
+ commandLineBuilder.setExePath(baseInterpreterPath)
+
+ execution.pythonScriptPath?.let { commandLineBuilder.addParameter(it.apply(targetEnvironment)) }
+ ?: throw IllegalArgumentException("Python script path must be set")
+
+ execution.parameters.forEach { parameter ->
+ val resolvedParameter = parameter.apply(targetEnvironment)
+ if (resolvedParameter != PythonExecution.SKIP_ARGUMENT) {
+ commandLineBuilder.addParameter(resolvedParameter)
+ }
+ }
+
+
+ for ((name, value) in execution.envs) {
+ commandLineBuilder.addEnvironmentVariable(name, value.apply(targetEnvironment))
+ }
+
+ val targetedCommandLine = commandLineBuilder.build()
+
+
+ // todo poerty/pipenv
+ //val targetedCommandLineBuilder = TargetedCommandLineBuilder(request.targetEnvironmentRequest)
+ //targetedCommandLineBuilder.exePath = TargetValue.fixed("")
+ //val targetedCommandLine = targetedCommandLineBuilder.build()
+
+ val process = targetEnvironment.createProcess(targetedCommandLine)
+
+ val handler = CapturingProcessHandler(process, targetedCommandLine.charset, targetedCommandLine.getCommandPresentation(targetEnvironment))
+
+ val output = runWithModalProgressBlocking(ModalTaskOwner.guess(), "creating venv") {
+ handler.runProcess(60 * 1000)
+ }
+
+ PySdkSettings.instance.preferredVirtualEnvBaseSdk = baseInterpreterPath
+}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/sdk/interpreters/detect.kt b/python/src/com/jetbrains/python/sdk/interpreters/detect.kt
new file mode 100644
index 000000000000..05431b5c6b06
--- /dev/null
+++ b/python/src/com/jetbrains/python/sdk/interpreters/detect.kt
@@ -0,0 +1,78 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.sdk.interpreters
+
+import com.intellij.execution.target.TargetConfigurationWithLocalFsAccess
+import com.intellij.execution.target.TargetEnvironmentConfiguration
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.util.UserDataHolder
+import com.intellij.openapi.util.UserDataHolderBase
+import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
+import com.jetbrains.python.sdk.TargetAndPath
+import com.jetbrains.python.sdk.add.v2.DetectedSelectableInterpreter
+import com.jetbrains.python.sdk.detectSdkPaths
+import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
+import com.jetbrains.python.sdk.targetEnvConfiguration
+import com.jetbrains.python.sdk.tryFindPythonBinaries
+import kotlin.io.path.pathString
+
+
+fun detectSystemInterpreters(projectDir: String?, module: Module?, targetConfiguration: TargetEnvironmentConfiguration?, existingSdks: List): List {
+ return if (targetConfiguration == null) detectLocalSystemInterpreters(module, existingSdks)
+ else detectSystemInterpretersOnTarget(targetConfiguration)
+}
+
+fun detectSystemInterpretersOnTarget(targetConfiguration: TargetEnvironmentConfiguration): List {
+ val targetWithMappedLocalVfs = PythonInterpreterTargetEnvironmentFactory.getTargetWithMappedLocalVfs(targetConfiguration)
+ if (targetWithMappedLocalVfs != null) {
+ val searchRoots = listOf("/usr/bin/", "/usr/local/bin/")
+ return searchRoots.flatMap { searchRoot ->
+ targetWithMappedLocalVfs.getLocalPath(searchRoot)?.tryFindPythonBinaries()?.mapNotNull {
+ val pythonBinaryPath = targetWithMappedLocalVfs.getTargetPath(it) ?: return@mapNotNull null
+ DetectedSelectableInterpreter(pythonBinaryPath, targetConfiguration)
+ } ?: emptyList()
+ }
+ }
+ else {
+ // TODO Try to execute `which python` or introspect the target
+ //val request = targetEnvironmentConfiguration.createEnvironmentRequest(project = null)
+ //request.prepareEnvironment(TargetProgressIndicator.EMPTY).createProcess()
+ return emptyList()
+ }
+}
+
+
+
+// todo
+// remove context -- it's only platform independent flavours
+//
+fun detectLocalSystemInterpreters(module: Module?, existingSdks: List): List {
+
+ if (module != null && module.isDisposed) return emptyList()
+ val existingPaths = existingSdks.mapTo(HashSet()) { it.homePath }
+
+ return PythonSdkFlavor.getApplicableFlavors(false)
+ .asSequence()
+ .flatMap { it.suggestLocalHomePaths(module, null) }
+ .map { it.pathString }
+ .filter { it !in existingPaths }
+ .map { DetectedSelectableInterpreter(it) }
+ .toList()
+
+ // todo sort
+
+ //return PythonSdkFlavor.getApplicableFlavors(false)
+ //
+ // .flatMap { flavor -> flavor.detectInterpreters(module, context, targetModuleSitsOn, existingPaths) } // sorting
+ //.sortedWith(compareBy({ it.guessedLanguageLevel },
+ // { it.homePath }).reversed())
+}
+
+
+private fun PythonSdkFlavor<*>.detectInterpreters(module: Module?,
+ context: UserDataHolder,
+ targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
+ existingPaths: HashSet): List {
+ return detectSdkPaths(module, context, targetModuleSitsOn, existingPaths)
+ .map { DetectedSelectableInterpreter(it, targetModuleSitsOn?.asTargetConfig) }
+}
\ No newline at end of file