mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[python] PY-79486: (WIP): show workspace members one level deeper.
Simplify frontend logic, show workspace members as children of their parent GitOrigin-RevId: 551adc0869323cb1e8318334aa830dfd37ec2337
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3d1bb84425
commit
ef0216df92
@@ -16,9 +16,8 @@ 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.ModuleDTO
|
||||
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
|
||||
@@ -43,10 +42,43 @@ import kotlinx.coroutines.withContext
|
||||
*/
|
||||
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()),
|
||||
private val modules: Map<ModuleName, ModuleCreateInfo>,
|
||||
) {
|
||||
|
||||
val modulesDTO: List<ModuleDTO>
|
||||
|
||||
init {
|
||||
val children = HashMap<ModuleName, MutableSet<ModuleName>>()
|
||||
|
||||
// Find parents
|
||||
for ((moduleName, createInfo) in modules) {
|
||||
children.putIfAbsent(moduleName, HashSet())
|
||||
when (createInfo) {
|
||||
is ModuleCreateInfo.CreateSdkInfoWrapper -> Unit
|
||||
is ModuleCreateInfo.SameAs -> {
|
||||
children.getOrPut(createInfo.parentModuleName) { HashSet() }.add(moduleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
//Map modules
|
||||
modulesDTO = modules.mapNotNull { (moduleName, createInfo) ->
|
||||
when (createInfo) {
|
||||
is ModuleCreateInfo.CreateSdkInfoWrapper -> {
|
||||
val version = when (val r = createInfo.createSdkInfo) {
|
||||
is CreateSdkInfo.ExistingEnv -> r.version
|
||||
is CreateSdkInfo.WillCreateEnv -> null
|
||||
}
|
||||
ModuleDTO(moduleName, createInfo.toolId.id, version, children[moduleName]!!.toList().sorted())
|
||||
}
|
||||
is ModuleCreateInfo.SameAs -> null
|
||||
}
|
||||
}
|
||||
.sortedBy { it.name }
|
||||
.toList()
|
||||
|
||||
|
||||
}
|
||||
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Create instance and save in [project]
|
||||
@@ -65,7 +97,7 @@ internal class ModulesSdkConfigurator private constructor(
|
||||
return instance
|
||||
}
|
||||
|
||||
private suspend fun getModulesWithoutSDKCreateInfo(project: Project): Map<ModuleName, Pair<ModuleCreateInfo, CreateSdkDTO>> = withBackgroundProgress(project, PySdkConfiguratorBundle.message("intellij.python.sdk.looking")) {
|
||||
private suspend fun getModulesWithoutSDKCreateInfo(project: Project): Map<ModuleName, ModuleCreateInfo> = 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()
|
||||
@@ -80,21 +112,19 @@ internal class ModulesSdkConfigurator private constructor(
|
||||
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))
|
||||
Pair(module.name, createInfoAndDTO)
|
||||
}
|
||||
}
|
||||
|
||||
private val logger = fileLogger()
|
||||
|
||||
private sealed interface ModuleCreateInfo {
|
||||
data class CreateSdkInfoWrapper(val createSdkInfo: CreateSdkInfo) : ModuleCreateInfo
|
||||
data class CreateSdkInfoWrapper(val createSdkInfo: CreateSdkInfo, val toolId: ToolId) : 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
|
||||
private suspend fun getModuleInfo(module: Module, configuratorsByTool: Map<ToolId, PyProjectSdkConfigurationExtension>): ModuleCreateInfo? = // Save on module level
|
||||
when (val r = module.suggestSdk()) {
|
||||
is SuggestedSdk.PyProjectIndependent -> {
|
||||
val tools = r.preferTools.map { configuratorsByTool[it]!! }
|
||||
@@ -105,23 +135,15 @@ internal class ModulesSdkConfigurator private constructor(
|
||||
}
|
||||
}
|
||||
is SuggestedSdk.SameAs -> {
|
||||
val createInfo = ModuleCreateInfo.SameAs(r.parentModule.name)
|
||||
Pair(createInfo, createInfo.asDTO())
|
||||
ModuleCreateInfo.SameAs(r.parentModule.name)
|
||||
}
|
||||
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.version
|
||||
is CreateSdkInfo.WillCreateEnv -> null
|
||||
}
|
||||
return Pair(ModuleCreateInfo.CreateSdkInfoWrapper(createSdkInfo), CreateSdkDTO.ConfigurableModule(version, toolId.id))
|
||||
}
|
||||
private fun CreateSdkInfoWithTool.asDTO(): ModuleCreateInfo = ModuleCreateInfo.CreateSdkInfoWrapper(createSdkInfo, toolId)
|
||||
|
||||
private fun ModuleCreateInfo.SameAs.asDTO(): CreateSdkDTO.SameAs = CreateSdkDTO.SameAs(parentModuleName)
|
||||
|
||||
/**
|
||||
* Key used to store instance in project by [create] to be used by [popModulesSDKConfigurator]
|
||||
@@ -140,7 +162,7 @@ internal class ModulesSdkConfigurator private constructor(
|
||||
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
|
||||
val createInfo = (modules[module.name] ?: error("No create info for module $module, caller broke the contract"))
|
||||
when (createInfo) {
|
||||
is ModuleCreateInfo.CreateSdkInfoWrapper -> {
|
||||
when (val r = createInfo.createSdkInfo.sdkCreator(false)) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.intellij.openapi.components.Service.Level
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.rpc.topics.sendToClient
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.SHOW_SDK_CONFIG_UI_TOPIC
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -32,9 +33,9 @@ internal suspend fun configureSdkAskingUser(project: Project) {
|
||||
askUserMutex.withLock {
|
||||
val moduleToSuggestedSdk = ModulesSdkConfigurator.create(project)
|
||||
val modulesDTO = moduleToSuggestedSdk.modulesDTO
|
||||
if (modulesDTO.modules.isNotEmpty()) {
|
||||
if (modulesDTO.isNotEmpty()) {
|
||||
// No need to send empty list
|
||||
SHOW_SDK_CONFIG_UI_TOPIC.sendToClient(project, modulesDTO)
|
||||
SHOW_SDK_CONFIG_UI_TOPIC.sendToClient(project, ModulesDTO(modulesDTO))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,25 +6,18 @@ 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>)
|
||||
data class ModuleDTO(
|
||||
val name: ModuleName,
|
||||
val createdByTool: ToolIdDTO,
|
||||
val existingPyVersion: @NlsSafe String?,
|
||||
val childModules: List<ModuleName>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class ModulesDTO(val modules: List<ModuleDTO>)
|
||||
@@ -9,13 +9,12 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.python.sdkConfigurator.common.impl.CreateSdkDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModuleDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
|
||||
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 kotlinx.collections.immutable.PersistentList
|
||||
import org.jetbrains.jewel.ui.Orientation
|
||||
import org.jetbrains.jewel.ui.component.*
|
||||
import org.jetbrains.jewel.ui.icon.IconKey
|
||||
@@ -23,15 +22,14 @@ import org.jetbrains.jewel.ui.icon.IconKey
|
||||
|
||||
@Composable
|
||||
internal fun ModuleList(
|
||||
moduleItems: ImmutableMap<ModuleName, CreateSdkDTO>,
|
||||
moduleItems: PersistentList<ModuleDTO>,
|
||||
icons: ImmutableMap<ToolIdDTO, IconKey>,
|
||||
checked: SnapshotStateSet<String>,
|
||||
onCheck: (ModuleName) -> Unit,
|
||||
topLabel: @Nls String,
|
||||
projectStructureLabel: @Nls String,
|
||||
environmentLabel: @Nls String,
|
||||
topLabel: String,
|
||||
projectStructureLabel: String,
|
||||
environmentLabel: String,
|
||||
) {
|
||||
val moduleItems = remember { moduleItems.entries.sortedBy { it.key } }
|
||||
Box {
|
||||
VerticallyScrollableContainer {
|
||||
val colPadding = 2.dp
|
||||
@@ -46,9 +44,20 @@ internal fun ModuleList(
|
||||
Text(environmentLabel, modifier = modifier)
|
||||
}
|
||||
Divider(Orientation.Horizontal)
|
||||
for ((moduleName, moduleInfo) in moduleItems) {
|
||||
for (module in moduleItems) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = horizontalArrangement) {
|
||||
Module(moduleName, moduleName in checked, onCheck, colPadding, moduleInfo, icons)
|
||||
Module(module.name in checked, onCheck, colPadding, module, icons)
|
||||
} // Module children
|
||||
for (childModule in module.childModules) {
|
||||
CheckboxRow(
|
||||
childModule,
|
||||
childModule in checked,
|
||||
onCheckedChange = {},
|
||||
softWrap = false,
|
||||
maxLines = 1,
|
||||
enabled = false,
|
||||
modifier = Modifier.padding(start = colPadding + 26.dp) // Checkbox width
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,45 +67,33 @@ internal fun ModuleList(
|
||||
|
||||
@Composable
|
||||
private fun RowScope.Module(
|
||||
moduleName: @NlsSafe String,
|
||||
checked: Boolean,
|
||||
onCheck: (ModuleName) -> Unit,
|
||||
columnPadding: Dp,
|
||||
moduleInfo: CreateSdkDTO,
|
||||
moduleInfo: ModuleDTO, // For parent only
|
||||
icons: ImmutableMap<ToolIdDTO, IconKey>,
|
||||
) {
|
||||
val colModifier = Modifier.weight(1f)
|
||||
val newText = remember { PySdkConfiguratorFrontendBundle.message("python.sdk.configurator.frontend.choose.modules.new") }
|
||||
val parent = when (moduleInfo) {
|
||||
is CreateSdkDTO.ConfigurableModule -> null
|
||||
is CreateSdkDTO.SameAs -> moduleInfo.parentModuleName
|
||||
}
|
||||
val elementToChange = parent ?: moduleName // TODO: move logic out of UI
|
||||
val moduleName = moduleInfo.name
|
||||
CheckboxRow(
|
||||
moduleName,
|
||||
softWrap = false,
|
||||
maxLines = 1,
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
onCheck(elementToChange)
|
||||
onCheck(moduleName)
|
||||
},
|
||||
modifier = colModifier
|
||||
)
|
||||
|
||||
Row(colModifier.padding(start = columnPadding)) {
|
||||
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)
|
||||
}
|
||||
val text = moduleInfo.existingPyVersion?.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(text, Modifier.clickable(onClick = { onCheck(elementToChange) }), maxLines = 1, softWrap = false)
|
||||
Text(text, Modifier.clickable(onClick = { onCheck(moduleName) }), maxLines = 1, softWrap = false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,11 @@ 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.ModuleDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ToolIdDTO
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.*
|
||||
import org.jetbrains.jewel.bridge.icon.fromPlatformIcon
|
||||
import org.jetbrains.jewel.ui.icon.IconKey
|
||||
import org.jetbrains.jewel.ui.icon.IntelliJIconKey
|
||||
@@ -19,35 +18,29 @@ import org.jetbrains.jewel.ui.icon.IntelliJIconKey
|
||||
* Result can be taken from [checked]
|
||||
*/
|
||||
internal class ModulesViewModel(modulesDTO: ModulesDTO) {
|
||||
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)
|
||||
val icons: PersistentMap<ToolIdDTO, IconKey> = persistentMapOf(*modulesDTO.modules
|
||||
.mapNotNull { module ->
|
||||
val toolId = module.createdByTool
|
||||
val icon = getIcon(ToolId(toolId))?.let { IntelliJIconKey.fromPlatformIcon(it.first, it.second) } ?: return@mapNotNull null
|
||||
Pair(toolId, icon)
|
||||
}.toTypedArray())
|
||||
|
||||
val checkBoxItems: PersistentList<ModuleDTO> = modulesDTO.modules.toPersistentList()
|
||||
val checked: SnapshotStateSet<ModuleName> = mutableStateSetOf()
|
||||
private val children = mutableMapOf<ModuleName, MutableSet<ModuleName>>()
|
||||
|
||||
init {
|
||||
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)
|
||||
}
|
||||
}
|
||||
// parent -> children
|
||||
private val children: ImmutableMap<ModuleName, ImmutableSet<ModuleName>> = checkBoxItems.associate {
|
||||
Pair(it.name, it.childModules.toPersistentSet())
|
||||
}.toPersistentMap()
|
||||
|
||||
fun clicked(what: ModuleName) {
|
||||
val toChange = setOf(what) + children.getOrDefault(what, emptySet())
|
||||
// child -> parent
|
||||
private val parents: ImmutableMap<ModuleName, ModuleName> = children.flatMap { (parent, children) ->
|
||||
children.map { Pair(it, parent) }
|
||||
}.toMap().toImmutableMap()
|
||||
|
||||
fun clicked(module: ModuleName) {
|
||||
val what = parents[module] ?: module // Get parent if child module
|
||||
val toChange = setOf(what) + children.getOrDefault(what, emptySet()) // Get children if parent
|
||||
val checkBoxSet = what !in checked
|
||||
if (checkBoxSet) {
|
||||
checked.addAll(toChange)
|
||||
|
||||
@@ -41,7 +41,7 @@ private class MyDialog(project: Project, private val viewModel: ModulesViewModel
|
||||
ModuleList(viewModel.checkBoxItems,
|
||||
viewModel.icons,
|
||||
viewModel.checked,
|
||||
viewModel::clicked,
|
||||
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"))
|
||||
|
||||
Reference in New Issue
Block a user