mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +07:00
[python] PY-79486 (WIP): Use real EPs to create SDKs.
The process is described in `ModulesSdkConfigurator` doc. GitOrigin-RevId: 1a21824e488a2d799b229d7c8355b60b0b177809
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a1b92b2591
commit
a35535b51a
@@ -5,6 +5,7 @@ import javax.swing.Icon
|
||||
|
||||
|
||||
/**
|
||||
* Get icon for certain tool id
|
||||
* Get icon for certain tool id.
|
||||
* This icon comes along with class because jewel needs it
|
||||
*/
|
||||
fun getIcon(id: ToolId): Icon? = ToolIdToIconMapper.EP.extensionList.firstOrNull { it.id == id }?.icon
|
||||
fun getIcon(id: ToolId): Pair<Icon, Class<*>>? = ToolIdToIconMapper.EP.extensionList.firstOrNull { it.id == id }?.let { Pair(it.icon, it.clazz) }
|
||||
@@ -9,7 +9,12 @@ interface ToolIdToIconMapper {
|
||||
val id: ToolId
|
||||
val icon: Icon
|
||||
|
||||
/**
|
||||
* Class with icons (jewel requires it)
|
||||
*/
|
||||
val clazz: Class<*>
|
||||
|
||||
companion object {
|
||||
internal val EP = ExtensionPointName.create<ToolIdToIconMapper>("com.intellij.python.common.toolToIconMapper")
|
||||
internal val EP = ExtensionPointName.create<ToolIdToIconMapper>("com.intellij.python.common.toolToIconMapper")
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<python.common.toolToIconMapper implementation="com.intellij.pycharm.community.ide.impl.configuration.PyReqToolIdToIconMapper"/>
|
||||
<projectService serviceInterface="com.intellij.psi.search.ProjectScopeBuilder"
|
||||
serviceImplementation="com.intellij.pycharm.community.ide.impl.PyProjectScopeBuilder"
|
||||
overrides="true"/>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.pycharm.community.ide.impl.configuration
|
||||
|
||||
import com.intellij.python.common.tools.ToolId
|
||||
import com.intellij.python.common.tools.spi.ToolIdToIconMapper
|
||||
import com.jetbrains.python.icons.PythonIcons
|
||||
import javax.swing.Icon
|
||||
|
||||
internal class PyReqToolIdToIconMapper : ToolIdToIconMapper {
|
||||
override val id: ToolId = PY_REQ_TOOL_ID
|
||||
override val icon: Icon = PythonIcons.Python.Virtualenv
|
||||
override val clazz: Class<*> = PythonIcons::class.java
|
||||
}
|
||||
@@ -45,10 +45,12 @@ import kotlin.io.path.Path
|
||||
|
||||
private val LOGGER = fileLogger()
|
||||
|
||||
internal val PY_REQ_TOOL_ID = ToolId("requirements.txt")
|
||||
|
||||
@ApiStatus.Internal
|
||||
class PyRequirementsTxtOrSetupPySdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
|
||||
override val toolId: ToolId = ToolId("PyRequirements") // This is nonsense, but will be dropped soon
|
||||
override val toolId: ToolId = PY_REQ_TOOL_ID // This is nonsense, but will be dropped soon
|
||||
|
||||
override suspend fun checkEnvironmentAndPrepareSdkCreator(module: Module): CreateSdkInfo? = prepareSdkCreator(
|
||||
{ checkManageableEnv(module) },
|
||||
|
||||
@@ -8,4 +8,6 @@ import javax.swing.Icon
|
||||
internal class HatchIdMapper : ToolIdToIconMapper {
|
||||
override val id: ToolId = HATCH_TOOL_ID
|
||||
override val icon: Icon = PythonHatchIcons.Logo
|
||||
|
||||
override val clazz: Class<*> = PythonHatchIcons::class.java
|
||||
}
|
||||
@@ -10,4 +10,5 @@ import javax.swing.Icon
|
||||
internal class PoetryToolIdMapper : ToolIdToIconMapper {
|
||||
override val id: ToolId = POETRY_TOOL_ID
|
||||
override val icon: Icon = PythonCommunityImplPoetryCommonIcons.Poetry
|
||||
override val clazz: Class<*> = PythonCommunityImplPoetryCommonIcons::class.java
|
||||
}
|
||||
@@ -2,11 +2,11 @@ package com.intellij.python.pyproject.model.internal
|
||||
|
||||
import com.intellij.platform.workspace.jps.entities.ModuleEntity
|
||||
import com.intellij.platform.workspace.jps.entities.ModuleId
|
||||
import com.intellij.platform.workspace.storage.*
|
||||
import com.intellij.platform.workspace.storage.WorkspaceEntity
|
||||
import com.intellij.platform.workspace.storage.annotations.Parent
|
||||
import com.intellij.python.common.tools.ToolId
|
||||
|
||||
internal interface PyProjectTomlWorkspaceEntity : WorkspaceEntity {
|
||||
interface PyProjectTomlWorkspaceEntity : WorkspaceEntity {
|
||||
|
||||
// [tool, probablyWorkspaceRoot?]. If root is null -> tool didn't implement workspace, just participated in this entry creation
|
||||
val participatedTools: Map<ToolId, ModuleId?>
|
||||
@@ -15,6 +15,6 @@ internal interface PyProjectTomlWorkspaceEntity : WorkspaceEntity {
|
||||
val module: ModuleEntity
|
||||
}
|
||||
|
||||
internal val ModuleEntity.pyProjectTomlEntity: PyProjectTomlWorkspaceEntity?
|
||||
val ModuleEntity.pyProjectTomlEntity: PyProjectTomlWorkspaceEntity?
|
||||
by WorkspaceEntity.extension()
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ jvm_library(
|
||||
"//platform/remote-topics/shared:rpc-topics",
|
||||
"//python/python-sdk-configurator/common",
|
||||
"//python/common",
|
||||
"//platform/projectModel-impl",
|
||||
"//platform/workspace/jps",
|
||||
"//platform/workspace/storage",
|
||||
]
|
||||
)
|
||||
### auto-generated section `build intellij.python.sdkConfigurator.backend` end
|
||||
@@ -48,5 +48,8 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.rpc.topics" />
|
||||
<orderEntry type="module" module-name="intellij.python.sdkConfigurator.common" />
|
||||
<orderEntry type="module" module-name="intellij.python.common" />
|
||||
<orderEntry type="module" module-name="intellij.platform.projectModel.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.workspace.jps" />
|
||||
<orderEntry type="module" module-name="intellij.platform.workspace.storage" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -16,6 +16,10 @@
|
||||
<extensions defaultExtensionNs="com.intellij.platform">
|
||||
<rpc.backend.remoteApiProvider implementation="com.intellij.python.sdkConfigurator.backend.impl.rpcBridge.ApiProvider"/>
|
||||
</extensions>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<registryKey defaultValue="1" description="How many SDK lookup processes execute in parallel"
|
||||
key="intellij.python.sdkConfigurator.backend.sdk.parallel"/>
|
||||
</extensions>
|
||||
|
||||
<actions>
|
||||
<action class="com.intellij.python.sdkConfigurator.backend.impl.platformBridge.ConfigureSDKAction" id="ConfigureSDKAction"/>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
intellij.python.sdk.configuring.module=Configuring SDK for module {0}
|
||||
action.ConfigureSDKAction.text=Configure SDK Automatically
|
||||
action.ConfigureSDKAction.description=Configure SDK for all modules in project
|
||||
action.ConfigureSDKAction.description=Configure SDK for all modules in project
|
||||
intellij.python.sdk.looking=Looking for pythons
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package com.intellij.python.sdkConfigurator.backend.impl
|
||||
|
||||
import com.intellij.openapi.diagnostic.debug
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.modules
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.util.removeUserData
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.python.common.tools.ToolId
|
||||
import com.intellij.python.pyproject.model.api.SuggestedSdk
|
||||
import com.intellij.python.pyproject.model.api.suggestSdk
|
||||
import com.intellij.python.sdkConfigurator.backend.impl.ModulesSdkConfigurator.Companion.create
|
||||
import com.intellij.python.sdkConfigurator.backend.impl.ModulesSdkConfigurator.Companion.popModulesSDKConfigurator
|
||||
import com.intellij.python.sdkConfigurator.common.impl.CreateSdkDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.sdk.configuration.CreateSdkInfo
|
||||
import com.jetbrains.python.sdk.configuration.CreateSdkInfoWithTool
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
import com.jetbrains.python.sdk.setAssociationToPath
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Configures SDK for modules in [project].
|
||||
*
|
||||
* 1. Create instance with [create]
|
||||
* 2. Ask use to choose from [modulesDTO]
|
||||
* 3. Provide chosen names to [configureSdks]
|
||||
*
|
||||
* [create] saves instance in project, so you can get in by [popModulesSDKConfigurator]
|
||||
*/
|
||||
internal class ModulesSdkConfigurator private constructor(
|
||||
private val project: Project,
|
||||
private val modules: Map<ModuleName, Pair<ModuleCreateInfo, CreateSdkDTO>>,
|
||||
val modulesDTO: ModulesDTO = ModulesDTO(modules.map { Pair(it.key, it.value.second) }.toMap()),
|
||||
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Create instance and save in [project]
|
||||
*/
|
||||
suspend fun create(project: Project): ModulesSdkConfigurator = ModulesSdkConfigurator(project, getModulesWithoutSDKCreateInfo(project)).also {
|
||||
project.putUserData(key, it)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance from project and **clear it**
|
||||
*/
|
||||
fun Project.popModulesSDKConfigurator(): ModulesSdkConfigurator {
|
||||
val instance = getUserData(key)
|
||||
?: error("No ${ModulesSdkConfigurator::class.java} found in $this. Did you call ${::create} or did you already called this method?")
|
||||
removeUserData(key) // Drop prev. usage to prevent leak
|
||||
return instance
|
||||
}
|
||||
|
||||
private suspend fun getModulesWithoutSDKCreateInfo(project: Project): Map<ModuleName, Pair<ModuleCreateInfo, CreateSdkDTO>> = withBackgroundProgress(project, PySdkConfiguratorBundle.message("intellij.python.sdk.looking")) {
|
||||
val tools = PyProjectSdkConfigurationExtension.createMap()
|
||||
val limit = Semaphore(permits = Registry.intValue("intellij.python.sdkConfigurator.backend.sdk.parallel"))
|
||||
val now = System.currentTimeMillis()
|
||||
val resultDef = project.modules.filter { ModuleRootManager.getInstance(it).sdk == null }.map { module ->
|
||||
limit.withPermit {
|
||||
async {
|
||||
val moduleInfo = getModuleInfo(module, tools) ?: return@async null
|
||||
Pair(module, moduleInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
val result = resultDef.awaitAll().filterNotNull()
|
||||
logger.debug { "SDKs calculated in ${System.currentTimeMillis() - now}ms" }
|
||||
result.associate { (module, createInfoAndDTO) ->
|
||||
val (createInfo, dto) = createInfoAndDTO
|
||||
//module.putUserData(modulesKey, createInfo)
|
||||
Pair(module.name, Pair(createInfo, dto))
|
||||
}
|
||||
}
|
||||
|
||||
private val logger = fileLogger()
|
||||
|
||||
private sealed interface ModuleCreateInfo {
|
||||
data class CreateSdkInfoWrapper(val createSdkInfo: CreateSdkInfo) : ModuleCreateInfo
|
||||
data class SameAs(val parentModuleName: ModuleName) : ModuleCreateInfo
|
||||
}
|
||||
|
||||
|
||||
private suspend fun getModuleInfo(module: Module, configuratorsByTool: Map<ToolId, PyProjectSdkConfigurationExtension>): Pair<ModuleCreateInfo, CreateSdkDTO>? = // Save on module level
|
||||
when (val r = module.suggestSdk()) {
|
||||
is SuggestedSdk.PyProjectIndependent -> {
|
||||
val tools = r.preferTools.map { configuratorsByTool[it]!! }
|
||||
tools.firstNotNullOfOrNull { tool ->
|
||||
val createInfo = (tool.asPyProjectTomlSdkConfigurationExtension()?.createSdkWithoutPyProjectTomlChecks(module)
|
||||
?: tool.checkEnvironmentAndPrepareSdkCreator(module)) ?: return@firstNotNullOfOrNull null
|
||||
CreateSdkInfoWithTool(createInfo, tool.toolId).asDTO()
|
||||
}
|
||||
}
|
||||
is SuggestedSdk.SameAs -> {
|
||||
val createInfo = ModuleCreateInfo.SameAs(r.parentModule.name)
|
||||
Pair(createInfo, createInfo.asDTO())
|
||||
}
|
||||
null -> null
|
||||
} // No tools or not pyproject.toml at all? Use EP as a fallback
|
||||
?: PyProjectSdkConfigurationExtension.findAllSortedForModule(module).firstOrNull()?.let { CreateSdkInfoWithTool(it.createSdkInfo, it.toolId).asDTO() }
|
||||
|
||||
|
||||
private fun CreateSdkInfoWithTool.asDTO(): Pair<ModuleCreateInfo, CreateSdkDTO.ConfigurableModule> {
|
||||
val version = when (val r = createSdkInfo) {
|
||||
is CreateSdkInfo.ExistingEnv -> r.pythonInfo.languageLevel.toPythonVersion()
|
||||
is CreateSdkInfo.WillCreateEnv -> null
|
||||
}
|
||||
return Pair(ModuleCreateInfo.CreateSdkInfoWrapper(createSdkInfo), CreateSdkDTO.ConfigurableModule(version, toolId.id))
|
||||
}
|
||||
|
||||
private fun ModuleCreateInfo.SameAs.asDTO(): CreateSdkDTO.SameAs = CreateSdkDTO.SameAs(parentModuleName)
|
||||
|
||||
/**
|
||||
* Key used to store instance in project by [create] to be used by [popModulesSDKConfigurator]
|
||||
*/
|
||||
private val key = Key.create<ModulesSdkConfigurator>("pyModulesSDKConfigurator")
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures SDK for modules [modulesOnly]
|
||||
* Errors are logged.
|
||||
*
|
||||
*/
|
||||
suspend fun configureSdks(modulesOnly: Set<ModuleName>) {
|
||||
withContext(Dispatchers.Default) {
|
||||
val modulesMap = project.modules.associateBy { it.name }
|
||||
val modulesWithSameSdk = mutableMapOf<Module, Module>()
|
||||
for (module in modulesOnly.map { modulesMap[it] ?: error("No module $it, caller broke the contract") }) { // TODO: Run in parallel
|
||||
withBackgroundProgress(project, PySdkConfiguratorBundle.message("intellij.python.sdk.configuring.module", module.name)) {
|
||||
val createInfo = (modules[module.name] ?: error("No create info for module $module, caller broke the contract")).first
|
||||
when (createInfo) {
|
||||
is ModuleCreateInfo.CreateSdkInfoWrapper -> {
|
||||
when (val r = createInfo.createSdkInfo.sdkCreator(false)) {
|
||||
is Result.Failure -> { //TODO: Show SDK creation error?
|
||||
logger.warn("Failed to create SDK for ${module.name}: ${r.error}")
|
||||
}
|
||||
is Result.Success -> {
|
||||
val sdk = r.result!! // can't be `null` and will be non-null soon
|
||||
ModuleRootModificationUtil.setModuleSdk(module, sdk)
|
||||
}
|
||||
}
|
||||
}
|
||||
is ModuleCreateInfo.SameAs -> {
|
||||
val parent = modulesMap[createInfo.parentModuleName] ?: error("No parent module named ${createInfo.parentModuleName}")
|
||||
modulesWithSameSdk[module] = parent
|
||||
}
|
||||
}
|
||||
} // Link workspace members with their workspace
|
||||
val reportedBrokenModules = mutableSetOf<Module>()
|
||||
for ((module, parentModule) in modulesWithSameSdk) {
|
||||
val parentSdk = ModuleRootManager.getInstance(parentModule).sdk
|
||||
if (parentSdk != null) {
|
||||
ModuleRootModificationUtil.setModuleSdk(module, parentSdk) // This SDK is shared, no need to associate it
|
||||
// TODO: Support association with multiple modules
|
||||
if (parentSdk.getOrCreateAdditionalData().associatedModulePath != null) {
|
||||
parentSdk.setAssociationToPath(null)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (parentModule != reportedBrokenModules) {
|
||||
logger.warn("No sdk for workspace root ${parentModule}, all children will have no SDKs")
|
||||
}
|
||||
reportedBrokenModules.add(parentModule)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,24 +3,9 @@ package com.intellij.python.sdkConfigurator.backend.impl
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.Service.Level
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.modules
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.platform.rpc.topics.sendToClient
|
||||
import com.intellij.python.pyproject.model.api.SuggestedSdk
|
||||
import com.intellij.python.pyproject.model.api.suggestSdk
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.SHOW_SDK_CONFIG_UI_TOPIC
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.sdk.configuration.CreateSdkInfo
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
import com.jetbrains.python.sdk.setAssociationToPath
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -31,7 +16,7 @@ import kotlinx.coroutines.withContext
|
||||
private val askUserMutex = Mutex()
|
||||
|
||||
/**
|
||||
* Same as [configureSdkAutomatically] but in a separate coroutine
|
||||
* Same as [configureSdkAskingUser] but in a separate coroutine
|
||||
*/
|
||||
internal fun configureSdkAskingUserBg(project: Project) {
|
||||
project.service<MyService>().scope.launch(Dispatchers.Default) {
|
||||
@@ -45,122 +30,15 @@ internal fun configureSdkAskingUserBg(project: Project) {
|
||||
internal suspend fun configureSdkAskingUser(project: Project) {
|
||||
withContext(Dispatchers.Default) {
|
||||
askUserMutex.withLock {
|
||||
val moduleToSuggestedSdk = getModulesWithoutSDK(project)
|
||||
if (moduleToSuggestedSdk.modules.isNotEmpty()) {
|
||||
val moduleToSuggestedSdk = ModulesSdkConfigurator.create(project)
|
||||
val modulesDTO = moduleToSuggestedSdk.modulesDTO
|
||||
if (modulesDTO.modules.isNotEmpty()) {
|
||||
// No need to send empty list
|
||||
SHOW_SDK_CONFIG_UI_TOPIC.sendToClient(project, moduleToSuggestedSdk)
|
||||
SHOW_SDK_CONFIG_UI_TOPIC.sendToClient(project, modulesDTO)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures SDK for modules without SDK in automatic manner trying to fix as many modules as possible.
|
||||
* Errors are logged.
|
||||
*/
|
||||
internal suspend fun configureSdkAutomatically(project: Project, modulesOnly: Set<ModuleName>? = null) {
|
||||
withContext(Dispatchers.Default) {
|
||||
val modules = project.modules
|
||||
.filter { ModuleRootManager.getInstance(it).sdk == null }
|
||||
.filter { modulesOnly == null || it.name in modulesOnly }
|
||||
if (modules.isEmpty()) {
|
||||
// All modules have SDK
|
||||
return@withContext
|
||||
}
|
||||
val configurators = PyProjectSdkConfigurationExtension.EP_NAME.extensionList
|
||||
val configuratorsByTool = configurators
|
||||
.mapNotNull { extension -> extension.asPyProjectTomlSdkConfigurationExtension()?.toolId?.let { Pair(it, extension) } }
|
||||
.toMap()
|
||||
|
||||
assert(configurators.isNotEmpty()) { "PyCharm can't work without any SDK configurator" }
|
||||
|
||||
val tomlBasedConfigurators = configuratorsByTool.values
|
||||
val legacyConfigurators = configurators.filter { it.asPyProjectTomlSdkConfigurationExtension() == null }
|
||||
val allSortedConfigurators = tomlBasedConfigurators + legacyConfigurators
|
||||
|
||||
val modulesWithSameSdk = mutableMapOf<Module, Module>()
|
||||
for (module in modules) {
|
||||
// TODO: Run in parallel
|
||||
withBackgroundProgress(project, PySdkConfiguratorBundle.message("intellij.python.sdk.configuring.module", module.name)) {
|
||||
when (val r = module.suggestSdk()) {
|
||||
null -> {
|
||||
// Not a pyproject.toml: try all configurators
|
||||
configureSdkForModule(module, allSortedConfigurators, checkForIntention = true)
|
||||
}
|
||||
is SuggestedSdk.PyProjectIndependent -> {
|
||||
val preferredConfigurators = r.preferTools.mapNotNull { configuratorsByTool[it] }
|
||||
if (!configureSdkForModule(module, preferredConfigurators, checkForIntention = false)) {
|
||||
// For pyproject.toml based -- use pyproject.toml only configs
|
||||
configureSdkForModule(module, tomlBasedConfigurators - preferredConfigurators.toSet(), checkForIntention = true)
|
||||
}
|
||||
}
|
||||
is SuggestedSdk.SameAs -> {
|
||||
modulesWithSameSdk[module] = r.parentModule
|
||||
}
|
||||
}
|
||||
// Link workspace members with their workspace
|
||||
val reportedBrokenModules = mutableSetOf<Module>()
|
||||
for ((module, parentModule) in modulesWithSameSdk) {
|
||||
val parentSdk = ModuleRootManager.getInstance(parentModule).sdk
|
||||
if (parentSdk != null) {
|
||||
ModuleRootModificationUtil.setModuleSdk(module, parentSdk)
|
||||
// This SDK is shared, no need to associate it
|
||||
// TODO: Support association with multiple modules
|
||||
if (parentSdk.getOrCreateAdditionalData().associatedModulePath != null) {
|
||||
parentSdk.setAssociationToPath(null)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (parentModule != reportedBrokenModules) {
|
||||
logger.warn("No sdk for workspace root ${parentModule}, all children will have no SDKs")
|
||||
}
|
||||
reportedBrokenModules.add(parentModule)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getModulesWithoutSDK(project: Project): ModulesDTO =
|
||||
ModulesDTO(project.modules.filter { ModuleRootManager.getInstance(it).sdk == null }.associate { module ->
|
||||
val parent = when (val r = module.suggestSdk()) {
|
||||
null, is SuggestedSdk.PyProjectIndependent -> null
|
||||
is SuggestedSdk.SameAs -> r.parentModule
|
||||
}
|
||||
Pair(module.name, parent?.name)
|
||||
})
|
||||
|
||||
private suspend fun configureSdkForModule(module: Module, configurators: List<PyProjectSdkConfigurationExtension>, checkForIntention: Boolean): Boolean {
|
||||
// TODO: Parallelize call to checkEnvironmentAndPrepareSdkCreator
|
||||
val createSdkInfos = configurators.mapNotNull {
|
||||
if (checkForIntention) it.checkEnvironmentAndPrepareSdkCreator(module)
|
||||
else it.asPyProjectTomlSdkConfigurationExtension()?.createSdkWithoutPyProjectTomlChecks(module)
|
||||
}.sorted()
|
||||
|
||||
for (createSdkInfo in createSdkInfos) {
|
||||
val created = when (val r = createSdkInfo.sdkCreator(false)) {
|
||||
is Result.Failure -> {
|
||||
val msgExtraInfo = when (createSdkInfo) {
|
||||
is CreateSdkInfo.ExistingEnv -> " using existing environment "
|
||||
is CreateSdkInfo.WillCreateEnv -> " "
|
||||
}
|
||||
logger.warn("can't create SDK${msgExtraInfo}for ${module.name}: ${r.error.message}")
|
||||
false
|
||||
}
|
||||
is Result.Success -> r.result?.also { sdk ->
|
||||
ModuleRootModificationUtil.setModuleSdk(module, sdk)
|
||||
} != null
|
||||
}
|
||||
if (created) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private val logger = fileLogger()
|
||||
|
||||
|
||||
@Service(Level.PROJECT)
|
||||
private class MyService(val scope: CoroutineScope)
|
||||
private class MyService(val scope: CoroutineScope)
|
||||
|
||||
@@ -2,14 +2,14 @@ package com.intellij.python.sdkConfigurator.backend.impl.rpcBridge
|
||||
|
||||
import com.intellij.platform.project.ProjectId
|
||||
import com.intellij.platform.project.findProject
|
||||
import com.intellij.python.sdkConfigurator.backend.impl.ModulesSdkConfigurator.Companion.popModulesSDKConfigurator
|
||||
import com.intellij.python.sdkConfigurator.backend.impl.configureSdkAskingUser
|
||||
import com.intellij.python.sdkConfigurator.backend.impl.configureSdkAutomatically
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
|
||||
import com.intellij.python.sdkConfigurator.common.impl.SdkConfiguratorBackEndApi
|
||||
|
||||
internal object SdkConfiguratorApiImpl : SdkConfiguratorBackEndApi {
|
||||
override suspend fun configureSdkAutomatically(projectId: ProjectId, onlyModules: Set<ModuleName>) {
|
||||
configureSdkAutomatically(projectId.findProject(), onlyModules)
|
||||
override suspend fun configureSdkForModules(projectId: ProjectId, onlyModules: Set<ModuleName>) {
|
||||
projectId.findProject().popModulesSDKConfigurator().configureSdks(onlyModules)
|
||||
}
|
||||
|
||||
override suspend fun configureAskingUser(projectId: ProjectId) {
|
||||
|
||||
@@ -35,6 +35,7 @@ jvm_library(
|
||||
"//platform/util/coroutines",
|
||||
"//platform/project/shared:project",
|
||||
"//platform/remote-topics/shared:rpc-topics",
|
||||
"//python/common",
|
||||
]
|
||||
)
|
||||
### auto-generated section `build intellij.python.sdkConfigurator.common` end
|
||||
@@ -47,5 +47,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
|
||||
<orderEntry type="module" module-name="intellij.platform.project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.rpc.topics" />
|
||||
<orderEntry type="module" module-name="intellij.python.common" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,5 +1,9 @@
|
||||
<idea-plugin>
|
||||
<idea-plugin visibility="internal">
|
||||
<dependencies>
|
||||
<module name="intellij.python.common"/>
|
||||
</dependencies>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<registryKey defaultValue="false" description="Configure SDK for modules which lack thereof in automatic manner" key="intellij.python.sdkConfigurator.auto" restartRequired="true"/>
|
||||
<registryKey defaultValue="false" description="Configure SDK for modules which lack thereof in automatic manner"
|
||||
key="intellij.python.sdkConfigurator.auto" restartRequired="true"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.intellij.platform.rpc.topics.ProjectRemoteTopic
|
||||
import fleet.rpc.RemoteApi
|
||||
import fleet.rpc.Rpc
|
||||
import fleet.rpc.remoteApiDescriptor
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Front calls back
|
||||
@@ -14,29 +13,25 @@ import kotlinx.serialization.Serializable
|
||||
@Rpc
|
||||
interface SdkConfiguratorBackEndApi : RemoteApi<Unit> {
|
||||
/***
|
||||
* Configure SDK for all modules in [projectId] if their names in [onlyModules] unconditionally
|
||||
* Configure SDK for all modules in [projectId] if their names in [onlyModules]
|
||||
*/
|
||||
suspend fun configureSdkAutomatically(projectId: ProjectId, onlyModules: Set<ModuleName>)
|
||||
suspend fun configureSdkForModules(projectId: ProjectId, onlyModules: Set<ModuleName>)
|
||||
|
||||
/**
|
||||
* Ask user about modules, then call [configureSdkAutomatically]
|
||||
* Ask user about modules, then call [configureSdkForModules]
|
||||
*/
|
||||
suspend fun configureAskingUser(projectId: ProjectId)
|
||||
}
|
||||
|
||||
typealias ModuleName = String
|
||||
|
||||
/**
|
||||
* Ask user to choose from [ModulesDTO] and then call [SdkConfiguratorBackEndApi.configureSdkAutomatically]
|
||||
* Ask user to choose from [ModulesDTO] and then call [SdkConfiguratorBackEndApi.configureSdkForModules]
|
||||
*/
|
||||
val SHOW_SDK_CONFIG_UI_TOPIC: ProjectRemoteTopic<ModulesDTO> = ProjectRemoteTopic("PySDKConfigurationUITopic", ModulesDTO.serializer())
|
||||
|
||||
/**
|
||||
* Module to parent (workspace) or null if module doesn't have a parent (not a part of workspace)
|
||||
*/
|
||||
@Serializable
|
||||
data class ModulesDTO(val modules: Map<ModuleName, ModuleName?>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* [SdkConfiguratorBackEndApi] instance
|
||||
|
||||
30
python/python-sdk-configurator/common/src/impl/moduleDTOs.kt
Normal file
30
python/python-sdk-configurator/common/src/impl/moduleDTOs.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.intellij.python.sdkConfigurator.common.impl
|
||||
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
typealias ModuleName = @NlsSafe String
|
||||
typealias ToolIdDTO = @NlsSafe String // value classes aren't serializable by default
|
||||
|
||||
// Serializable DTO that reflects regular object is, unfortunately, recommended approach
|
||||
|
||||
@Serializable
|
||||
sealed interface CreateSdkDTO {
|
||||
/**
|
||||
* This module is part of workspace and parent is [parentModuleName]
|
||||
*/
|
||||
@Serializable
|
||||
data class SameAs(val parentModuleName: ModuleName) : CreateSdkDTO
|
||||
|
||||
/**
|
||||
* [createdByTool] can create an SDK for this module (if [existingVersion] is not null, venv is already exists on disk)
|
||||
*/
|
||||
@Serializable
|
||||
data class ConfigurableModule(val existingVersion: @NlsSafe String?, val createdByTool: ToolIdDTO) : CreateSdkDTO
|
||||
}
|
||||
|
||||
/**
|
||||
* Module and how do we create it
|
||||
*/
|
||||
@Serializable
|
||||
data class ModulesDTO(val modules: Map<ModuleName, CreateSdkDTO>)
|
||||
@@ -39,6 +39,7 @@ jvm_library(
|
||||
"//libraries/compose-foundation-desktop",
|
||||
"//platform/jewel/foundation",
|
||||
"//libraries/kotlinx/collections-immutable:libraries-kotlinx-collections-immutable",
|
||||
"//python/common",
|
||||
],
|
||||
plugins = ["@lib//:compose-plugin"]
|
||||
)
|
||||
|
||||
@@ -52,5 +52,6 @@
|
||||
<orderEntry type="module" module-name="intellij.libraries.compose.foundation.desktop" />
|
||||
<orderEntry type="module" module-name="intellij.platform.jewel.foundation" />
|
||||
<orderEntry type="module" module-name="intellij.libraries.kotlinx.collections.immutable" />
|
||||
<orderEntry type="module" module-name="intellij.python.common" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -9,6 +9,7 @@
|
||||
<module name="intellij.platform.jewel.foundation"/>
|
||||
<module name="intellij.platform.jewel.ui"/>
|
||||
<module name="intellij.platform.jewel.ideLafBridge"/>
|
||||
<module name="intellij.python.common"/>
|
||||
</dependencies>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<platform.rpc.projectRemoteTopicListener
|
||||
|
||||
@@ -2,3 +2,6 @@ python.sdk.configurator.frontend.choose.modules.title=Configure Modules Environm
|
||||
python.sdk.configurator.frontend.choose.modules.text=Pick the ones you want to use to run code and set up environment for them
|
||||
python.sdk.configurator.frontend.choose.modules.project.structure=Project Structure
|
||||
python.sdk.configurator.frontend.choose.modules.environment=Environment
|
||||
python.sdk.configurator.frontend.choose.modules.workspace.member=Same as {0}
|
||||
python.sdk.configurator.frontend.choose.modules.workspace.existing=Use existing version {0}
|
||||
python.sdk.configurator.frontend.choose.modules.new=Create new environment
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
package com.intellij.python.sdkConfigurator.frontend;
|
||||
|
||||
import com.intellij.DynamicBundle;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
final class PySdkConfiguratorFrontendBundle extends DynamicBundle {
|
||||
@ApiStatus.Internal
|
||||
public final class PySdkConfiguratorFrontendBundle extends DynamicBundle {
|
||||
public static final @NonNls String BUNDLE = "messages.PySdkConfiguratorFrontendBundle";
|
||||
public static final PySdkConfiguratorFrontendBundle INSTANCE = new PySdkConfiguratorFrontendBundle();
|
||||
|
||||
|
||||
@@ -6,26 +6,36 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateSet
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.intellij.python.sdkConfigurator.frontend.ModuleInfo
|
||||
import com.intellij.python.sdkConfigurator.common.impl.CreateSdkDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ToolIdDTO
|
||||
import com.intellij.python.sdkConfigurator.frontend.PySdkConfiguratorFrontendBundle
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.jewel.foundation.theme.JewelTheme
|
||||
import org.jetbrains.jewel.ui.component.CheckboxRow
|
||||
import org.jetbrains.jewel.ui.component.Icon
|
||||
import org.jetbrains.jewel.ui.component.Text
|
||||
import org.jetbrains.jewel.ui.component.VerticallyScrollableContainer
|
||||
import org.jetbrains.jewel.ui.icon.IconKey
|
||||
|
||||
|
||||
@Composable
|
||||
internal fun ModuleList(
|
||||
moduleItems: ImmutableMap<String, ModuleInfo>,
|
||||
moduleItems: ImmutableMap<String, CreateSdkDTO>,
|
||||
icons: ImmutableMap<ToolIdDTO, IconKey>,
|
||||
checked: SnapshotStateSet<String>,
|
||||
onCheckChange: (String, Boolean) -> Unit,
|
||||
topLabel: @Nls String,
|
||||
projectStructureLabel: @Nls String,
|
||||
environmentLabel: @Nls String,
|
||||
) {
|
||||
val newText = remember { PySdkConfiguratorFrontendBundle.message("python.sdk.configurator.frontend.choose.modules.new") }
|
||||
val ts = JewelTheme.defaultTextStyle
|
||||
val padding = 2.dp
|
||||
val longestItemChars = remember { (listOf(projectStructureLabel) + moduleItems.keys).maxBy { it.length } }
|
||||
@@ -41,7 +51,10 @@ internal fun ModuleList(
|
||||
VerticallyScrollableContainer {
|
||||
Column(Modifier, horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(padding)) {
|
||||
for ((moduleName, moduleInfo) in moduleItems) {
|
||||
val (parent, pythons) = moduleInfo
|
||||
val parent = when (moduleInfo) {
|
||||
is CreateSdkDTO.ConfigurableModule -> null
|
||||
is CreateSdkDTO.SameAs -> moduleInfo.parentModuleName
|
||||
}
|
||||
Row(Modifier.padding(padding), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(spaceBetweenCols)) {
|
||||
val checked = moduleName in checked
|
||||
val elementToChange = parent ?: moduleName // TODO: move logic out of UI
|
||||
@@ -54,12 +67,32 @@ internal fun ModuleList(
|
||||
textStyle = ts,
|
||||
textModifier = Modifier.width(leftColumnMinSize)
|
||||
)
|
||||
if (parent == null) {
|
||||
PythonsDropDown(pythons)
|
||||
val text = when (moduleInfo) {
|
||||
is CreateSdkDTO.ConfigurableModule -> {
|
||||
val text = moduleInfo.existingVersion?.let { PySdkConfiguratorFrontendBundle.message("python.sdk.configurator.frontend.choose.modules.workspace.existing", it) }
|
||||
?: newText
|
||||
val icon = icons[moduleInfo.createdByTool]
|
||||
if (icon != null) {
|
||||
Icon(icon, text)
|
||||
}
|
||||
text
|
||||
}
|
||||
is CreateSdkDTO.SameAs -> {
|
||||
PySdkConfiguratorFrontendBundle.message("python.sdk.configurator.frontend.choose.modules.workspace.member", moduleInfo.parentModuleName)
|
||||
}
|
||||
}
|
||||
Text(text, Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun measureText(text: String, textStyle: TextStyle): Dp {
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
return with(LocalDensity.current) {
|
||||
textMeasurer.measure(text, textStyle).size.width.toDp()
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.intellij.python.sdkConfigurator.frontend.components
|
||||
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import org.jetbrains.jewel.foundation.ExperimentalJewelApi
|
||||
import org.jetbrains.jewel.foundation.theme.JewelTheme
|
||||
import org.jetbrains.jewel.ui.component.ListComboBox
|
||||
import org.jetbrains.jewel.ui.component.SimpleListItem
|
||||
import org.jetbrains.jewel.ui.icons.AllIconsKeys.Language.Python
|
||||
|
||||
@OptIn(ExperimentalJewelApi::class)
|
||||
@Composable
|
||||
internal fun PythonsDropDown(pythons: ImmutableList<String>, grayedOut: Boolean = false, modifier: Modifier = Modifier) {
|
||||
val ts = JewelTheme.defaultTextStyle
|
||||
val longestPython = remember { pythons.maxBy { it.length } }
|
||||
val padding = 100.dp
|
||||
val width = measureText(longestPython, ts) + padding // Padding
|
||||
var i by remember { mutableIntStateOf(0) }
|
||||
ListComboBox(
|
||||
items = pythons,
|
||||
selectedIndex = i,
|
||||
modifier = modifier.widthIn(min = width, max = width + padding),
|
||||
onSelectedItemChange = { i = it },
|
||||
itemKeys = { index, _ -> index },
|
||||
itemContent = { item, isSelected, isActive ->
|
||||
SimpleListItem(
|
||||
text = item,
|
||||
selected = isSelected,
|
||||
active = isActive,
|
||||
icon = Python,
|
||||
colorFilter = if (grayedOut) ColorFilter.tint(Color.Gray) else null,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.intellij.python.sdkConfigurator.frontend.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
@Composable
|
||||
internal fun measureText(text: String, textStyle: TextStyle): Dp {
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
return with(LocalDensity.current) {
|
||||
textMeasurer.measure(text, textStyle).size.width.toDp()
|
||||
}
|
||||
}
|
||||
@@ -2,30 +2,46 @@ package com.intellij.python.sdkConfigurator.frontend
|
||||
|
||||
import androidx.compose.runtime.mutableStateSetOf
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateSet
|
||||
import com.intellij.python.common.tools.ToolId
|
||||
import com.intellij.python.common.tools.getIcon
|
||||
import com.intellij.python.sdkConfigurator.common.impl.CreateSdkDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ToolIdDTO
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
|
||||
private val fakePythons = arrayOf("Python 3.10", "Python 3.11")
|
||||
import org.jetbrains.jewel.bridge.icon.fromPlatformIcon
|
||||
import org.jetbrains.jewel.ui.icon.IconKey
|
||||
import org.jetbrains.jewel.ui.icon.IntelliJIconKey
|
||||
|
||||
/**
|
||||
* UI should display [checkBoxItems] (either enabled or disabled). On each click call [clicked].
|
||||
* Result can be taken from [checked]
|
||||
*/
|
||||
internal class ModulesViewModel(modulesDTO: ModulesDTO) {
|
||||
val checkBoxItems: PersistentMap<ModuleName, ModuleInfo> = persistentMapOf(*modulesDTO.modules
|
||||
.map { (moduleName, parent) ->
|
||||
Pair(moduleName, ModuleInfo(parent, pythons = persistentListOf(*fakePythons)))
|
||||
val icons: PersistentMap<ToolIdDTO, IconKey> = persistentMapOf(*modulesDTO.modules.values.mapNotNull {
|
||||
when (it) {
|
||||
is CreateSdkDTO.ConfigurableModule -> it.createdByTool
|
||||
is CreateSdkDTO.SameAs -> null
|
||||
}
|
||||
}.mapNotNull { toolId ->
|
||||
val icon = getIcon(ToolId(toolId))?.let { IntelliJIconKey.fromPlatformIcon(it.first, it.second) } ?: return@mapNotNull null
|
||||
Pair(toolId, icon)
|
||||
}.toTypedArray())
|
||||
|
||||
val checkBoxItems: PersistentMap<ModuleName, CreateSdkDTO> = persistentMapOf(*modulesDTO.modules
|
||||
.map { (moduleName, createSdkInfo) ->
|
||||
Pair(moduleName, createSdkInfo)
|
||||
}.toTypedArray())
|
||||
val checked: SnapshotStateSet<ModuleName> = mutableStateSetOf()
|
||||
private val children = mutableMapOf<ModuleName, MutableSet<ModuleName>>()
|
||||
|
||||
init {
|
||||
for ((child, parent) in modulesDTO.modules) {
|
||||
if (parent == null) continue
|
||||
for ((child, createSdkDTO) in modulesDTO.modules) {
|
||||
val parent = when (createSdkDTO) {
|
||||
is CreateSdkDTO.ConfigurableModule -> continue
|
||||
is CreateSdkDTO.SameAs -> createSdkDTO.parentModuleName
|
||||
}
|
||||
children.getOrPut(parent) { HashSet() }.add(child)
|
||||
}
|
||||
}
|
||||
@@ -41,4 +57,3 @@ internal class ModulesViewModel(modulesDTO: ModulesDTO) {
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ModuleInfo(val parent: String?, val pythons: ImmutableList<String>)
|
||||
|
||||
@@ -23,7 +23,7 @@ internal class FrontendTopicListener : ProjectRemoteTopicListener<ModulesDTO> {
|
||||
// Ask user to choose modules, then ask backend to configure it
|
||||
askUser(project, event) { modulesChosenByUser ->
|
||||
scope.launch {
|
||||
SdkConfiguratorBackEndApi().configureSdkAutomatically(project.projectId(), modulesChosenByUser)
|
||||
SdkConfiguratorBackEndApi().configureSdkForModules(project.projectId(), modulesChosenByUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,10 @@ private class MyDialog(project: Project, private val viewModel: ModulesViewModel
|
||||
override fun createCenterPanel(): JComponent {
|
||||
enableNewSwingCompositing()
|
||||
return compose(focusOnClickInside = true, content = {
|
||||
ModuleList(viewModel.checkBoxItems, viewModel.checked, viewModel::clicked,
|
||||
ModuleList(viewModel.checkBoxItems,
|
||||
viewModel.icons,
|
||||
viewModel.checked,
|
||||
viewModel::clicked,
|
||||
topLabel = message("python.sdk.configurator.frontend.choose.modules.text"),
|
||||
projectStructureLabel = message("python.sdk.configurator.frontend.choose.modules.project.structure"),
|
||||
environmentLabel = message("python.sdk.configurator.frontend.choose.modules.environment"))
|
||||
|
||||
@@ -17,8 +17,12 @@ import org.jetbrains.annotations.CheckReturnValue
|
||||
@ApiStatus.Internal
|
||||
interface PyProjectSdkConfigurationExtension {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val EP_NAME: ExtensionPointName<PyProjectSdkConfigurationExtension> = ExtensionPointName.create("Pythonid.projectSdkConfigurationExtension")
|
||||
private val EP_NAME: ExtensionPointName<PyProjectSdkConfigurationExtension> = ExtensionPointName.create("Pythonid.projectSdkConfigurationExtension")
|
||||
|
||||
/**
|
||||
* EPs associated by tool id
|
||||
*/
|
||||
fun createMap(): Map<ToolId, PyProjectSdkConfigurationExtension> = EP_NAME.extensionList.associateBy { it.toolId }
|
||||
|
||||
/**
|
||||
* We return all configurators in a sorted order. The order is determined by extensions order, but existing environments have a
|
||||
|
||||
@@ -10,4 +10,5 @@ import javax.swing.Icon
|
||||
internal class UvToolIdMapper : ToolIdToIconMapper {
|
||||
override val id: ToolId = UV_TOOL_ID
|
||||
override val icon: Icon = PythonCommunityImplUVCommonIcons.UV
|
||||
override val clazz: Class<*> = PythonCommunityImplUVCommonIcons::class.java
|
||||
}
|
||||
Reference in New Issue
Block a user