mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
[python] initial step of python interpreter creation unification (PY-73396)
- refactored NPW to use target-specific model - creating interpreter through widget reuses the same UI (can be turned off by python.unified.interpreter.configuration) - interpreter discovery and virtualenv creation using Targets API - no more PyDetectedSdk in dialog (cherry picked from commit 581b2d40254d26f02eb3aa61bc2e842854b87a3e) IJ-MR-140986 GitOrigin-RevId: be29188304882ef5f0fb88bb60c538714a2d8746
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2ce10a0738
commit
7e95d1c688
@@ -626,6 +626,8 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
|
||||
<!-- Parameter Info -->
|
||||
<registryKey key="python.parameter.info.show.all.hints" defaultValue="false"
|
||||
description="Show type hints for all parameters in parameter info window"/>
|
||||
<registryKey key="python.unified.interpreter.configuration" defaultValue="true"
|
||||
description="Use the same UI to configure interpreters in IDE widget and New Project Wizard"/>
|
||||
</extensions>
|
||||
|
||||
<extensionPoints>
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.net.URL
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
|
||||
private val LOG: Logger = logger<Sdks>()
|
||||
val LOG: Logger = logger<Sdks>()
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Sdk>)
|
||||
: 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,
|
||||
|
||||
@@ -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<TargetAndPath>,
|
||||
): List<PyDetectedSdk> =
|
||||
private fun PythonSdkFlavor<*>.detectSdks(module: Module?,
|
||||
context: UserDataHolder,
|
||||
targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
|
||||
existingPaths: HashSet<TargetAndPath>): List<PyDetectedSdk> =
|
||||
detectSdkPaths(module, context, targetModuleSitsOn, existingPaths)
|
||||
.map { createDetectedSdk(it, targetModuleSitsOn?.asTargetConfig, this) }
|
||||
|
||||
|
||||
internal fun PythonSdkFlavor<*>.detectSdkPaths(module: Module?,
|
||||
context: UserDataHolder,
|
||||
targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
|
||||
existingPaths: HashSet<TargetAndPath>): List<String> =
|
||||
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)
|
||||
|
||||
|
||||
@@ -65,6 +65,19 @@ private suspend fun detectSystemWideSdksSuspended(module: Module?,
|
||||
{ it.homePath }).reversed())
|
||||
}
|
||||
|
||||
private suspend fun detectSystemWideInterpreters(module: Module?,
|
||||
existingSdks: List<Sdk>,
|
||||
target: TargetEnvironmentConfiguration? = null,
|
||||
context: UserDataHolder): List<Sdk> {
|
||||
if (module != null && module.isDisposed) return emptyList()
|
||||
val effectiveTarget = target ?: module?.let { PythonInterpreterTargetEnvironmentFactory.getTargetModuleResidesOn(it) }?.asTargetConfig
|
||||
val baseDirFromContext = context.getUserData(BASE_DIR)
|
||||
return service<PySdks>().getOrDetectSdks(effectiveTarget, baseDirFromContext)
|
||||
.filter { detectedSdk -> existingSdks.none(detectedSdk::isSameAs) }
|
||||
.sortedWith(compareBy<PyDetectedSdk>({ 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<Path> =
|
||||
internal fun Path.tryFindPythonBinaries(): List<Path> =
|
||||
runCatching { Files.list(this).filter(Path::looksLikePythonBinary).collect(Collectors.toList()) }.getOrElse { emptyList() }
|
||||
|
||||
private fun Path.looksLikePythonBinary(): Boolean =
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<PyCondaEnv?>
|
||||
private val selectedEnvironment = propertyGraph.property<PyCondaEnv?>(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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<LanguageLevel>
|
||||
private lateinit var versionComboBox: ComboBox<LanguageLevel>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<Sdk?>(initial = null)
|
||||
private lateinit var pipEnvPathField: TextFieldWithBrowseButton
|
||||
private lateinit var basePythonComboBox: ComboBox<Sdk?>
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<Sdk?>(initial = null)
|
||||
private lateinit var basePythonComboBox: ComboBox<Sdk?>
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<PythonInterpreterCreationTargets>
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<PythonAddInterpreterPresenter>()
|
||||
val LOG = logger<PythonAddInterpreterPresenter>()
|
||||
|
||||
private fun MutableStateFlow<List<PyDetectedSdk>>.addDetectedSdk(targetPath: String,
|
||||
targetEnvironmentConfiguration: TargetEnvironmentConfiguration?) {
|
||||
|
||||
@@ -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<String>,
|
||||
val scope: CoroutineScope,
|
||||
val basePythonSdks: ObservableMutableProperty<List<Sdk>>,
|
||||
val allExistingSdks: ObservableMutableProperty<List<Sdk>>,
|
||||
val installableSdks: ObservableMutableProperty<List<Sdk>>,
|
||||
val basePythonSdks: ObservableMutableProperty<List<Sdk>>, // todo replace with flow, local properties for every creator
|
||||
val allExistingSdks: ObservableMutableProperty<List<Sdk>>, // todo merge with allSdks, replace with flow and local properties
|
||||
val installableSdks: ObservableMutableProperty<List<Sdk>>, // todo not needed
|
||||
val selectedVenv: ObservableMutableProperty<Sdk?>,
|
||||
val condaExecutable: ObservableMutableProperty<String>,
|
||||
) {
|
||||
|
||||
@@ -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<String>,
|
||||
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<List<Sdk>>(emptyList())
|
||||
private val basePythonSdks = propertyGraph.property<List<Sdk>>(emptyList())
|
||||
private val installableSdks = propertyGraph.property<List<Sdk>>(emptyList())
|
||||
private val pythonBaseVersion = propertyGraph.property<Sdk?>(null)
|
||||
private val selectedVenv = propertyGraph.property<Sdk?>(null)
|
||||
|
||||
private val condaExecutable = propertyGraph.property("")
|
||||
private var venvHint = propertyGraph.property("")
|
||||
|
||||
private lateinit var pythonBaseVersionComboBox: ComboBox<Sdk?>
|
||||
|
||||
private lateinit var pythonBaseVersionComboBox: PythonInterpreterComboBox
|
||||
private var initialized = false
|
||||
|
||||
private fun updateVenvLocationHint() {
|
||||
@@ -69,23 +57,18 @@ class PythonAddNewEnvironmentPanel(val projectPath: ObservableProperty<String>,
|
||||
else if (get == BASE_CONDA && PROJECT_VENV in allowedInterpreterTypes) venvHint.set(message("sdk.create.simple.conda.hint"))
|
||||
}
|
||||
|
||||
val state = PythonAddInterpreterState(propertyGraph,
|
||||
projectPath,
|
||||
service<PythonAddSdkService>().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<PythonAddSdkService>().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<String>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
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<String>,
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<Any, PythonAddEnvironment>()
|
||||
//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<Any, PythonAddEnvironment>()
|
||||
//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))
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Sdk?>(initial = null)
|
||||
private lateinit var versionComboBox: ComboBox<Sdk?>
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<Sdk?>,
|
||||
val presenter: PythonAddInterpreterPresenter,
|
||||
class PythonSdkComboBoxWithBrowseButtonEditor(val comboBox: ComboBox<PythonSelectableInterpreter?>,
|
||||
val controller: PythonAddInterpreterModel,
|
||||
onPathSelected: (String) -> Unit) : ComboBoxEditor {
|
||||
private val component = SimpleColoredComponent()
|
||||
private val panel: JComponent
|
||||
@@ -79,22 +72,32 @@ class PythonSdkComboBoxWithBrowseButtonEditor(val comboBox: ComboBox<Sdk?>,
|
||||
}
|
||||
})
|
||||
|
||||
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<Sdk?>,
|
||||
}
|
||||
|
||||
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<Sdk?>,
|
||||
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
|
||||
|
||||
@@ -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>): Sdk = when (
|
||||
newSdk
|
||||
}
|
||||
else -> sdk
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal fun setupSdkIfDetected(interpreter: PythonSelectableInterpreter, existingSdks: List<Sdk>, 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
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
78
python/src/com/jetbrains/python/sdk/add/v2/dialogs.kt
Normal file
78
python/src/com/jetbrains/python/sdk/add/v2/dialogs.kt
Normal file
@@ -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<PythonAddSdkService>().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
|
||||
}
|
||||
312
python/src/com/jetbrains/python/sdk/add/v2/models.kt
Normal file
312
python/src/com/jetbrains/python/sdk/add/v2/models.kt
Normal file
@@ -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<String>? = 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<List<PythonSelectableInterpreter>> = MutableStateFlow(emptyList())
|
||||
internal val detectedInterpreters: MutableStateFlow<List<PythonSelectableInterpreter>> = MutableStateFlow(emptyList())
|
||||
val manuallyAddedInterpreters: MutableStateFlow<List<PythonSelectableInterpreter>> = MutableStateFlow(emptyList())
|
||||
private var installable: List<PythonSelectableInterpreter> = emptyList()
|
||||
val condaEnvironments: MutableStateFlow<List<PyCondaEnv>> = MutableStateFlow(emptyList())
|
||||
|
||||
var allInterpreters: StateFlow<List<PythonSelectableInterpreter>> = combine(knownInterpreters,
|
||||
detectedInterpreters,
|
||||
manuallyAddedInterpreters) { known, detected, added ->
|
||||
added + known + detected
|
||||
}
|
||||
.stateIn(scope + uiContext, started = SharingStarted.Eagerly, initialValue = emptyList())
|
||||
|
||||
|
||||
val baseInterpreters: StateFlow<List<PythonSelectableInterpreter>> = 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<String>? = 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<String>? = 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<ExistingSelectableInterpreter>()
|
||||
.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<ProjectPathWithContext> =
|
||||
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<PythonSelectableInterpreter?> = propertyGraph.property(null)
|
||||
val condaExecutable: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val selectedCondaEnv: ObservableMutableProperty<PyCondaEnv?> = propertyGraph.property(null)
|
||||
val baseCondaEnv: ObservableMutableProperty<PyCondaEnv?> = propertyGraph.property(null)
|
||||
}
|
||||
|
||||
class MutableTargetState(propertyGraph: PropertyGraph) : AddInterpreterState(propertyGraph) {
|
||||
val baseInterpreter: ObservableMutableProperty<PythonSelectableInterpreter?> = propertyGraph.property(null)
|
||||
val newCondaEnvName: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val poetryExecutable: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val pipenvExecutable: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val venvPath: ObservableMutableProperty<String> = propertyGraph.property("")
|
||||
val inheritSitePackages = propertyGraph.property(false)
|
||||
val makeAvailable = propertyGraph.property(false)
|
||||
}
|
||||
|
||||
|
||||
|
||||
val PythonAddInterpreterModel.existingSdks
|
||||
get() = allInterpreters.value.filterIsInstance<ExistingSelectableInterpreter>().map { it.sdk }
|
||||
|
||||
val PythonAddInterpreterModel.baseSdks
|
||||
get() = baseInterpreters.value.filterIsInstance<ExistingSelectableInterpreter>().map { it.sdk }
|
||||
|
||||
fun PythonAddInterpreterModel.findInterpreter(path: String): PythonSelectableInterpreter? {
|
||||
return allInterpreters.value.asSequence().find { it.homePath == path}
|
||||
}
|
||||
107
python/src/com/jetbrains/python/sdk/add/v2/presentersExt.kt
Normal file
107
python/src/com/jetbrains/python/sdk/add/v2/presentersExt.kt
Normal file
@@ -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
|
||||
}
|
||||
@@ -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)!!)
|
||||
}
|
||||
@@ -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 <T> PropertyGraph.booleanProperty(dependency: ObservableProperty<T>
|
||||
lazyProperty { dependency.get() == value }.apply { dependsOn(dependency) { dependency.get() == value } }
|
||||
|
||||
class PythonNewEnvironmentDialogNavigator {
|
||||
lateinit var selectionMode: ObservableMutableProperty<PythonInterpreterSelectionMode>
|
||||
var selectionMode: ObservableMutableProperty<PythonInterpreterSelectionMode>? = null
|
||||
lateinit var selectionMethod: ObservableMutableProperty<PythonInterpreterSelectionMethod>
|
||||
lateinit var newEnvManager: ObservableMutableProperty<PythonSupportedEnvironmentManagers>
|
||||
lateinit var existingEnvManager: ObservableMutableProperty<PythonSupportedEnvironmentManagers>
|
||||
@@ -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<Any>() {
|
||||
|
||||
override fun getListCellRendererComponent(list: JList<out Any>?, 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<out Any>, 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<Any>() {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Row.pythonInterpreterComboBox(selectedSdkProperty: ObservableMutableProperty<Sdk?>,
|
||||
presenter: PythonAddInterpreterPresenter,
|
||||
sdksFlow: StateFlow<List<Sdk>>,
|
||||
onPathSelected: (String) -> Unit): Cell<ComboBox<Sdk?>> =
|
||||
comboBox<Sdk?>(emptyList(), PythonSdkComboBoxListCellRenderer())
|
||||
internal fun Row.pythonInterpreterComboBox(selectedSdkProperty: ObservableMutableProperty<PythonSelectableInterpreter?>, // todo not sdk
|
||||
model: PythonAddInterpreterModel,
|
||||
onPathSelected: (String) -> Unit, busyState: StateFlow<Boolean>? = null): Cell<PythonInterpreterComboBox> {
|
||||
|
||||
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<PythonSelectableInterpreter?>,
|
||||
val controller: PythonAddInterpreterModel,
|
||||
val onPathSelected: (String) -> Unit) : ComboBox<PythonSelectableInterpreter?>() {
|
||||
|
||||
private lateinit var itemsFlow: StateFlow<List<PythonSelectableInterpreter>>
|
||||
val items: List<PythonSelectableInterpreter>
|
||||
get() = itemsFlow.value
|
||||
|
||||
private val interpreterToSelect = controller.propertyGraph.property<String?>(null)
|
||||
|
||||
init {
|
||||
renderer = PythonSdkComboBoxListCellRenderer()
|
||||
val newOnPathSelected: (String) -> Unit = {
|
||||
interpreterToSelect.set(it)
|
||||
onPathSelected(it)
|
||||
}
|
||||
editor = PythonSdkComboBoxWithBrowseButtonEditor(this, controller, newOnPathSelected)
|
||||
|
||||
}
|
||||
|
||||
fun setItems(flow: StateFlow<List<PythonSelectableInterpreter>>) {
|
||||
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>): Sdk? {
|
||||
val preferredSdkPath = PySdkSettings.instance.preferredVirtualEnvBaseSdk
|
||||
@@ -374,12 +453,15 @@ fun Panel.executableSelector(executable: ObservableMutableProperty<String>,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
92
python/src/com/jetbrains/python/sdk/add/v2/virtualenv.kt
Normal file
92
python/src/com/jetbrains/python/sdk/add/v2/virtualenv.kt
Normal file
@@ -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<Sdk>,
|
||||
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
|
||||
}
|
||||
78
python/src/com/jetbrains/python/sdk/interpreters/detect.kt
Normal file
78
python/src/com/jetbrains/python/sdk/interpreters/detect.kt
Normal file
@@ -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<Sdk>): List<DetectedSelectableInterpreter> {
|
||||
return if (targetConfiguration == null) detectLocalSystemInterpreters(module, existingSdks)
|
||||
else detectSystemInterpretersOnTarget(targetConfiguration)
|
||||
}
|
||||
|
||||
fun detectSystemInterpretersOnTarget(targetConfiguration: TargetEnvironmentConfiguration): List<DetectedSelectableInterpreter> {
|
||||
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<Sdk>): List<DetectedSelectableInterpreter> {
|
||||
|
||||
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<PyDetectedSdk>({ it.guessedLanguageLevel },
|
||||
// { it.homePath }).reversed())
|
||||
}
|
||||
|
||||
|
||||
private fun PythonSdkFlavor<*>.detectInterpreters(module: Module?,
|
||||
context: UserDataHolder,
|
||||
targetModuleSitsOn: TargetConfigurationWithLocalFsAccess?,
|
||||
existingPaths: HashSet<TargetAndPath>): List<DetectedSelectableInterpreter> {
|
||||
return detectSdkPaths(module, context, targetModuleSitsOn, existingPaths)
|
||||
.map { DetectedSelectableInterpreter(it, targetModuleSitsOn?.asTargetConfig) }
|
||||
}
|
||||
Reference in New Issue
Block a user