From 94d6ccfd55b76e95fdb4b69afcded8f51b1a2cbd Mon Sep 17 00:00:00 2001 From: Fabrizio Scarponi <36624359+fscarponi@users.noreply.github.com> Date: Thu, 25 Jan 2024 15:54:20 +0100 Subject: [PATCH] Fixed KMP-Platforms and attribute information in infoPanel (#30) (#36) Fixed KMP-Platforms and attribute information for InfoPanel Fixed warning log readability in KMP-modifier module Simplified the KMP-Attributes computation for KMP packages (remote or declared) Clarify ui events by using more accurate names Clarified tabs in InfoPanel, by changing tab name from Platforms to KMP-Platforms Refactor code for readability in PackageSearchInfoPanel (cherry picked from commit fac3e91e0c0b89338228e96938f338c6b59908bf) --- .../MppDependencyModifier.kt | 94 +++++-- .../resolved/MppGradleProjectResolver.kt | 66 +++-- .../core/data/PackageSearchDeclaredPackage.kt | 38 +++ .../plugin/gradle/KMPAttributes.kt | 61 ++-- .../packagesearch/plugin/gradle/KMPUtils.kt | 71 ++--- .../ui/model/infopanel/InfoPanelContent.kt | 37 ++- .../ui/model/infopanel/InfoPanelViewModel.kt | 8 +- .../ui/model/infopanel/asPanelContent.kt | 265 ++++++++++-------- .../ui/panels/side/HeaderAttributesTab.kt | 11 +- .../ui/panels/side/PackageSearchInfoPanel.kt | 17 +- .../messages/packageSearchBundle.properties | 2 +- 11 files changed, 363 insertions(+), 307 deletions(-) diff --git a/kmp-modifier/src/main/kotlin/com/intellij/packageSearch/mppDependencyUpdater/MppDependencyModifier.kt b/kmp-modifier/src/main/kotlin/com/intellij/packageSearch/mppDependencyUpdater/MppDependencyModifier.kt index fdadebec..ef23e752 100644 --- a/kmp-modifier/src/main/kotlin/com/intellij/packageSearch/mppDependencyUpdater/MppDependencyModifier.kt +++ b/kmp-modifier/src/main/kotlin/com/intellij/packageSearch/mppDependencyUpdater/MppDependencyModifier.kt @@ -16,11 +16,11 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.module.Module import com.intellij.packageSearch.mppDependencyUpdater.dsl.models.KotlinDslModel import com.intellij.packageSearch.mppDependencyUpdater.dsl.models.SourceSetModel -import com.intellij.util.alsoIfNull private val LOG = logger() object MppDependencyModifier { + @Suppress("unused") suspend fun isAvailable(module: Module): Boolean = readFromKotlinModel(module) { it.isAvailable } ?: false @@ -72,13 +72,20 @@ object MppDependencyModifier { val artifactSpec = mppDependency.artifactDependencySpec() val sourceSetModel = model.getOrCreateSourceSet(sourceSet, createIfMissing) - .alsoIfNull { LOG.warn("Requested source set ${sourceSet} not found") } - ?: return - sourceSetModel.getOrCreateDependenciesBlock(createIfMissing) - ?.addArtifact(mppDependency.configuration, artifactSpec) - .alsoIfNull { LOG.warn("Could not find dependencies block in source set ${sourceSet}") } - ?: return + if (sourceSetModel == null) { + LOG.warn("Requested source set ${sourceSet} not found") + return + } + + val dependenciesBlockModel = sourceSetModel.getOrCreateDependenciesBlock(createIfMissing) + + if (dependenciesBlockModel == null) { + LOG.warn("Could not find dependencies block in source set ${sourceSet}") + return + } + + dependenciesBlockModel.addArtifact(mppDependency.configuration, artifactSpec) } suspend fun removeDependency( @@ -109,16 +116,22 @@ object MppDependencyModifier { private fun removeFromKotlinModel(model: KotlinDslModel, sourceSet: String, dependency: MppDependency.Maven) { val sourceSetModel = model.sourceSets()?.get(sourceSet) - .alsoIfNull { LOG.warn("Requested source set ${sourceSet} not found") } - ?: return + + if (sourceSetModel == null) { + LOG.warn("Requested source set ${sourceSet} not found") + return + } val dependencies = sourceSetModel.dependencies() - .alsoIfNull { LOG.warn("Could not find dependencies block in source set ${sourceSet}") } - ?: return + if (dependencies == null) { + LOG.warn("Could not find dependencies block in source set ${sourceSet}") + return + } + + dependencies.findDependency(dependency) + ?.let { dependencies.remove(it) } + ?: LOG.warn("Could not find dependency $dependency") - dependencies.findDependency(dependency)?.also { dependencies.remove(it) } - .alsoIfNull { LOG.warn("Could not find dependency $dependency") } - ?: return } suspend fun updateDependency( @@ -164,28 +177,46 @@ object MppDependencyModifier { sourceSet: String, ) { val sourceSetModel = model.sourceSets()?.get(sourceSet) - .alsoIfNull { LOG.warn("Requested source set ${sourceSet} not found") } - ?: return + if (sourceSetModel == null) { + LOG.warn("Requested source set ${sourceSet} not found") + return + } + val dependencies = sourceSetModel.dependencies() - .alsoIfNull { LOG.warn("Dependencies block not found in source set ${sourceSet}") } - ?: return + if (dependencies == null) { + LOG.warn("Dependencies block not found in source set ${sourceSet}") + return + } + val artifactDependencyModel = dependencies.findDependency(oldDescriptor) - .alsoIfNull { LOG.warn("Could not find dependency $oldDescriptor") } - ?: return + if (artifactDependencyModel == null) { + LOG.warn("Could not find dependency $oldDescriptor") + return + } artifactDependencyModel.updateByDescriptor(oldDescriptor, newDescriptor) } - private fun KotlinDslModel.getOrCreateSourceSet(sourceSet: String, createIfMissing: Boolean): SourceSetModel? = - sourceSets()?.get(sourceSet) - .alsoIfNull { LOG.warn("Requested source set $sourceSet not found") } - ?: if (createIfMissing) declareSourceSet(sourceSet) else null + private fun KotlinDslModel.getOrCreateSourceSet(sourceSet: String, createIfMissing: Boolean): SourceSetModel? { + val foundSourceSet = sourceSets()?.get(sourceSet) + if (foundSourceSet != null) { + return foundSourceSet + } + + LOG.warn("Requested source set $sourceSet not found") + return if (createIfMissing) declareSourceSet(sourceSet) else null + } + + private fun SourceSetModel.getOrCreateDependenciesBlock(createIfMissing: Boolean): DependenciesModel? { + val dependenciesModel = dependencies() + if (dependenciesModel != null) { + return dependenciesModel + } - private fun SourceSetModel.getOrCreateDependenciesBlock(createIfMissing: Boolean): DependenciesModel? = - dependencies() - .alsoIfNull { LOG.warn("Dependencies block not found in source set $name") } - ?: if (createIfMissing) addDependenciesBlock() else null + LOG.warn("Dependencies block not found in source set $name") + return if (createIfMissing) addDependenciesBlock() else null + } private fun ArtifactDependencyModel.updateByDescriptor( oldDescriptor: MppDependency.Maven, @@ -262,8 +293,11 @@ object MppDependencyModifier { } } - private fun Module.buildModel() = ProjectBuildModel.get(project).getModuleBuildModel(this) - .alsoIfNull { LOG.warn("Could not create gradle model for module $this") } + private fun Module.buildModel(): GradleBuildModel? { + val buildModel = ProjectBuildModel.get(project).getModuleBuildModel(this) + if (buildModel == null) LOG.warn("Could not create gradle model for module $this") + return buildModel + } private fun MppDependency.Maven.artifactDependencySpec(): ArtifactDependencySpec = ArtifactDependencySpec.create( diff --git a/kmp-modifier/src/main/kotlin/com/intellij/packageSearch/mppDependencyUpdater/resolved/MppGradleProjectResolver.kt b/kmp-modifier/src/main/kotlin/com/intellij/packageSearch/mppDependencyUpdater/resolved/MppGradleProjectResolver.kt index 31b1a39f..a3f4c473 100644 --- a/kmp-modifier/src/main/kotlin/com/intellij/packageSearch/mppDependencyUpdater/resolved/MppGradleProjectResolver.kt +++ b/kmp-modifier/src/main/kotlin/com/intellij/packageSearch/mppDependencyUpdater/resolved/MppGradleProjectResolver.kt @@ -2,7 +2,6 @@ package com.intellij.packageSearch.mppDependencyUpdater.resolved import com.intellij.openapi.diagnostic.logger -import com.intellij.util.alsoIfNull import org.jetbrains.kotlin.idea.gradleJava.configuration.KotlinMppGradleProjectResolver import org.jetbrains.kotlin.idea.gradleJava.configuration.mpp.KotlinMppGradleProjectResolverExtension import org.jetbrains.kotlin.idea.projectModel.KotlinCompilation @@ -11,36 +10,43 @@ import org.jetbrains.kotlin.idea.projectModel.KotlinPlatform private val LOG = logger() class MppGradleProjectResolver : KotlinMppGradleProjectResolverExtension { - private fun KotlinMppGradleProjectResolver.Context.compilationToSourceSetMap(): Map> = - mppModel.targets.flatMap { target -> - target.compilations.flatMap { compilation -> - compilation.allSourceSets.map { sourceSet -> - compilation.toCompilationModel()?.let { sourceSet.name to it } + private fun KotlinMppGradleProjectResolver.Context.compilationToSourceSetMap(): Map> = + mppModel.targets.flatMap { target -> + target.compilations.flatMap { compilation -> + compilation.allSourceSets.map { sourceSet -> + compilation.toCompilationModel()?.let { sourceSet.name to it } + } + } } - } - } - .filterNotNull() - .groupBy { it.first } - .mapValues { entry -> entry.value.map { it.second }.toSet() } + .filterNotNull() + .groupBy { it.first } + .mapValues { entry -> entry.value.map { it.second }.toSet() } - private fun KotlinCompilation.toCompilationModel(): MppCompilationInfoModel.Compilation? = - when (platform) { - KotlinPlatform.COMMON -> MppCompilationInfoModel.Common - KotlinPlatform.JVM -> MppCompilationInfoModel.Jvm - KotlinPlatform.JS -> if (compilerArguments?.contains(MppCompilationInfoModel.Js.Compiler.IR.flag) == true) { - MppCompilationInfoModel.Js(MppCompilationInfoModel.Js.Compiler.IR) - } else { - MppCompilationInfoModel.Js(MppCompilationInfoModel.Js.Compiler.LEGACY) - } - KotlinPlatform.ANDROID -> MppCompilationInfoModel.Android - KotlinPlatform.WASM -> MppCompilationInfoModel.Wasm - KotlinPlatform.NATIVE -> nativeExtensions?.let { MppCompilationInfoModel.Native(it.konanTarget) } - .alsoIfNull { LOG.error("No native extensions found for the $name compilation") } - } + private fun KotlinCompilation.toCompilationModel(): MppCompilationInfoModel.Compilation? = + when (platform) { + KotlinPlatform.COMMON -> MppCompilationInfoModel.Common + KotlinPlatform.JVM -> MppCompilationInfoModel.Jvm + KotlinPlatform.JS -> if (compilerArguments?.contains(MppCompilationInfoModel.Js.Compiler.IR.flag) == true) { + MppCompilationInfoModel.Js(MppCompilationInfoModel.Js.Compiler.IR) + } else { + MppCompilationInfoModel.Js(MppCompilationInfoModel.Js.Compiler.LEGACY) + } - override fun afterResolveFinished(context: KotlinMppGradleProjectResolver.Context) { - val model = MppCompilationInfoModel(context.moduleDataNode.data.linkedExternalProjectPath, context.compilationToSourceSetMap()) - context.moduleDataNode.createChild(MppDataNodeProcessor.Util.MPP_SOURCES_SETS_MAP_KEY, model) - super.afterResolveFinished(context) - } + KotlinPlatform.ANDROID -> MppCompilationInfoModel.Android + KotlinPlatform.WASM -> MppCompilationInfoModel.Wasm + KotlinPlatform.NATIVE -> { + val nativeCompilationInfo = nativeExtensions?.let { MppCompilationInfoModel.Native(it.konanTarget) } + if (nativeCompilationInfo == null) LOG.error("No native extensions found for the $name compilation") + nativeCompilationInfo + } + } + + override fun afterResolveFinished(context: KotlinMppGradleProjectResolver.Context) { + val model = MppCompilationInfoModel( + context.moduleDataNode.data.linkedExternalProjectPath, + context.compilationToSourceSetMap() + ) + context.moduleDataNode.createChild(MppDataNodeProcessor.Util.MPP_SOURCES_SETS_MAP_KEY, model) + super.afterResolveFinished(context) + } } \ No newline at end of file diff --git a/plugin/core/src/main/kotlin/com/jetbrains/packagesearch/plugin/core/data/PackageSearchDeclaredPackage.kt b/plugin/core/src/main/kotlin/com/jetbrains/packagesearch/plugin/core/data/PackageSearchDeclaredPackage.kt index 83004a06..90cb174d 100644 --- a/plugin/core/src/main/kotlin/com/jetbrains/packagesearch/plugin/core/data/PackageSearchDeclaredPackage.kt +++ b/plugin/core/src/main/kotlin/com/jetbrains/packagesearch/plugin/core/data/PackageSearchDeclaredPackage.kt @@ -1,6 +1,8 @@ package com.jetbrains.packagesearch.plugin.core.data import com.jetbrains.packagesearch.plugin.core.extensions.DependencyDeclarationIndexes +import org.jetbrains.packagesearch.api.v3.ApiMavenPackage +import org.jetbrains.packagesearch.api.v3.ApiMavenPackage.GradleVersion.ApiVariant import org.jetbrains.packagesearch.api.v3.ApiPackage import org.jetbrains.packagesearch.packageversionutils.normalization.NormalizedVersion @@ -20,3 +22,39 @@ interface PackageSearchDeclaredPackage : IconProvider { val declaredScope: String? } + + +fun PackageSearchDeclaredPackage.listKMPAttributesNames(onlyStable: Boolean): Set { + val version = getLatestVersion(onlyStable) as? ApiMavenPackage.GradleVersion ?: return emptySet() + return version.listKMPAttributesNames() +} + +fun ApiPackage.listKMPAttributesNames(onlyStable: Boolean): Set { + val version = getLatestVersion(onlyStable) as? ApiMavenPackage.GradleVersion ?: return emptySet() + return version.listKMPAttributesNames() +} + +fun ApiMavenPackage.GradleVersion.listKMPAttributesNames(): Set = buildSet { + for (apiVariant in variants) { + val variantAttributes = + apiVariant.attributes["org.jetbrains.kotlin.platform.type"] as? ApiVariant.Attribute.ExactMatch + ?: continue + when (variantAttributes.value) { + "native" -> + when (val target = apiVariant.attributes["org.jetbrains.kotlin.native.target"]) { + is ApiVariant.Attribute.ExactMatch -> add(target.value) + else -> continue + } + + "common" -> continue + else -> add(variantAttributes.value) + } + } +} + +fun PackageSearchDeclaredPackage.getLatestVersion(onlyStable: Boolean) = + if (onlyStable) remoteInfo?.versions?.latestStable else remoteInfo?.versions?.latest + +fun ApiPackage.getLatestVersion(onlyStable: Boolean) = + if (onlyStable) versions.latestStable else versions.latest + diff --git a/plugin/gradle/kmp/src/main/kotlin/com/jetbrains/packagesearch/plugin/gradle/KMPAttributes.kt b/plugin/gradle/kmp/src/main/kotlin/com/jetbrains/packagesearch/plugin/gradle/KMPAttributes.kt index e06d60eb..6417ce8f 100644 --- a/plugin/gradle/kmp/src/main/kotlin/com/jetbrains/packagesearch/plugin/gradle/KMPAttributes.kt +++ b/plugin/gradle/kmp/src/main/kotlin/com/jetbrains/packagesearch/plugin/gradle/KMPAttributes.kt @@ -1,47 +1,20 @@ package com.jetbrains.packagesearch.plugin.gradle -import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModuleVariant -object KMPAttributes { - val iosArm64 = PackageSearchModuleVariant.Attribute.StringAttribute("ios_arm64") - val iosX64 = PackageSearchModuleVariant.Attribute.StringAttribute("ios_x64") - val ios = PackageSearchModuleVariant.Attribute.NestedAttribute("iOS", listOf(iosArm64, iosX64)) - - val macosX64 = PackageSearchModuleVariant.Attribute.StringAttribute("macos_x64") - val macosArm64 = PackageSearchModuleVariant.Attribute.StringAttribute("macos_arm64") - val macos = PackageSearchModuleVariant.Attribute.NestedAttribute("macOs", listOf(macosX64, macosArm64)) - - val watchosArm32 = PackageSearchModuleVariant.Attribute.StringAttribute("watchos_arm32") - val watchosArm64 = PackageSearchModuleVariant.Attribute.StringAttribute("watchos_arm64") - val watchosX64 = PackageSearchModuleVariant.Attribute.StringAttribute("watchos_x64") - val watchosDevice = - PackageSearchModuleVariant.Attribute.NestedAttribute("watchOs", listOf(watchosArm32, watchosArm64)) - val watchos = PackageSearchModuleVariant.Attribute.NestedAttribute("watchOs", listOf(watchosDevice, watchosX64)) - - val tvosArm64 = PackageSearchModuleVariant.Attribute.StringAttribute("tvos_arm64") - val tvosX64 = PackageSearchModuleVariant.Attribute.StringAttribute("tvos_x64") - val tvos = PackageSearchModuleVariant.Attribute.NestedAttribute("tvOs", listOf(tvosArm64, tvosX64)) - - val apple = PackageSearchModuleVariant.Attribute.NestedAttribute("Apple", listOf(ios, macos, watchos, tvos)) - - val jsLegacy = PackageSearchModuleVariant.Attribute.StringAttribute("jsLegacy") - val jsIr = PackageSearchModuleVariant.Attribute.StringAttribute("jsIr") - val js = PackageSearchModuleVariant.Attribute.NestedAttribute("JavaScript", listOf(jsLegacy, jsIr)) - - val linuxArm64 = PackageSearchModuleVariant.Attribute.StringAttribute("linuxArm64") - val linuxX64 = PackageSearchModuleVariant.Attribute.StringAttribute("linuxX64") - val linux = PackageSearchModuleVariant.Attribute.NestedAttribute("Linux", listOf(linuxArm64, linuxX64)) - - val android = PackageSearchModuleVariant.Attribute.StringAttribute("android") - - val androidArm32 = PackageSearchModuleVariant.Attribute.StringAttribute("androidArm32") - val androidArm64 = PackageSearchModuleVariant.Attribute.StringAttribute("androidArm64") - val androidX64 = PackageSearchModuleVariant.Attribute.StringAttribute("androidX64") - val androidX86 = PackageSearchModuleVariant.Attribute.StringAttribute("androidX86") - val androidNative = - PackageSearchModuleVariant.Attribute.NestedAttribute( - "Android Native", - listOf(androidArm32, androidArm64, androidX64, androidX86) - ) - -} \ No newline at end of file +data class KmpAttributesGroups( + val displayName: String, + val aggregationKeyword: String = displayName, +) + +val KMP_ATTRIBUTES_GROUPS = listOf( + KmpAttributesGroups("iOS"), + KmpAttributesGroups("macOS"), + KmpAttributesGroups("tvOS"), + KmpAttributesGroups("watchOS"), + KmpAttributesGroups("JS"), + KmpAttributesGroups("JVM"), + KmpAttributesGroups("Linux"), + KmpAttributesGroups("Android"), + KmpAttributesGroups("WASM"), + KmpAttributesGroups("Windows", "mingw"), +) diff --git a/plugin/gradle/kmp/src/main/kotlin/com/jetbrains/packagesearch/plugin/gradle/KMPUtils.kt b/plugin/gradle/kmp/src/main/kotlin/com/jetbrains/packagesearch/plugin/gradle/KMPUtils.kt index 0f6b44dd..17601dce 100644 --- a/plugin/gradle/kmp/src/main/kotlin/com/jetbrains/packagesearch/plugin/gradle/KMPUtils.kt +++ b/plugin/gradle/kmp/src/main/kotlin/com/jetbrains/packagesearch/plugin/gradle/KMPUtils.kt @@ -7,7 +7,7 @@ import com.jetbrains.packagesearch.plugin.core.data.PackageSearchDeclaredPackage import com.jetbrains.packagesearch.plugin.core.data.PackageSearchModuleVariant import kotlin.contracts.contract -fun Set.buildAttributes() = buildList { +fun Set.buildAttributes(): List { val rawStrings = this@buildAttributes.mapNotNull { when (it) { is MppCompilationInfoModel.Js -> when (it.compiler) { @@ -19,59 +19,30 @@ fun Set.buildAttributes() = buildList { MppCompilationInfoModel.Common -> null else -> it.platformId } - } - .toMutableSet() - - val hasIos = KMPAttributes.ios in rawStrings - val hasMacOs = KMPAttributes.macos in rawStrings - val hasTvOs = KMPAttributes.tvos in rawStrings - val hasWatchOs = KMPAttributes.watchos in rawStrings - - val hasApple = hasIos && hasMacOs && hasTvOs && hasWatchOs - - val hasJs = KMPAttributes.js in rawStrings - val hasAndroidNative = KMPAttributes.androidNative in rawStrings - val hasLinux = KMPAttributes.linux in rawStrings - when { - hasApple -> { - add(KMPAttributes.apple) - rawStrings.removeAll(KMPAttributes.apple.flatten()) - } + }.toSet() - else -> { - if (hasIos) { - add(KMPAttributes.ios) - rawStrings.removeAll(KMPAttributes.ios.flatten()) - } - if (hasMacOs) { - add(KMPAttributes.macos) - rawStrings.removeAll(KMPAttributes.macos.flatten()) - } - if (hasTvOs) { - add(KMPAttributes.tvos) - rawStrings.removeAll(KMPAttributes.tvos.flatten()) - } - if (hasWatchOs) { - add(KMPAttributes.watchos) - rawStrings.removeAll(KMPAttributes.watchos.flatten()) - } - } - } - if (hasJs) { - add(KMPAttributes.js) - rawStrings.removeAll(KMPAttributes.js.flatten()) - } - if (hasAndroidNative) { - add(KMPAttributes.androidNative) - rawStrings.removeAll(KMPAttributes.androidNative.flatten()) - } - if (hasLinux) { - add(KMPAttributes.linux) - rawStrings.removeAll(KMPAttributes.linux.flatten()) + return buildAttributesFromRawStrings(rawStrings) +} + +fun buildAttributesFromRawStrings(rawStrings: Set) = buildList { + var queue = rawStrings.toList() + for (attributeTitle in KMP_ATTRIBUTES_GROUPS) { + val (targets, rest) = queue + .partition { it.contains(attributeTitle.aggregationKeyword, true) } + + if (targets.isEmpty()) continue + add( + PackageSearchModuleVariant.Attribute.NestedAttribute( + attributeTitle.displayName, + targets.map { PackageSearchModuleVariant.Attribute.StringAttribute(it) }) + ) + queue = rest } - addAll(rawStrings.map { PackageSearchModuleVariant.Attribute.StringAttribute(it) }) + addAll(queue.map { PackageSearchModuleVariant.Attribute.StringAttribute(it) }) + } + operator fun Set.contains(attribute: PackageSearchModuleVariant.Attribute.NestedAttribute): Boolean = attribute.flatten().all { it in this } diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContent.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContent.kt index b08c40af..325ca8b7 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContent.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelContent.kt @@ -7,7 +7,12 @@ import com.jetbrains.packagesearch.plugin.ui.model.packageslist.PackageListItem sealed interface InfoPanelContent { - val tabTitle: String + data class TabTitleData( + val tabTitle: String, + val quantity: Int? = null, + ) + + val tabTitleData: TabTitleData sealed interface PackageInfo : InfoPanelContent { @@ -52,7 +57,7 @@ sealed interface InfoPanelContent { data class Base( override val packageListId: PackageListItem.Package.Declared.Id.Base, - override val tabTitle: String, + override val tabTitleData: TabTitleData, override val moduleId: PackageSearchModule.Identity, override val title: String, override val subtitle: String, @@ -70,12 +75,12 @@ sealed interface InfoPanelContent { override val availableVersions: List, override val declaredScope: String, override val availableScopes: List, - override val allowMissingScope: Boolean - ) : Declared + override val allowMissingScope: Boolean, + ) : Declared data class WithVariant( override val packageListId: PackageListItem.Package.Declared.Id.WithVariant, - override val tabTitle: String, + override val tabTitleData: TabTitleData, override val moduleId: PackageSearchModule.Identity, override val title: String, override val subtitle: String, @@ -96,7 +101,7 @@ sealed interface InfoPanelContent { override val allowMissingScope: Boolean, val declaredVariant: String, val compatibleVariants: List, - val variantTerminology: PackageSearchModule.WithVariants.Terminology + val variantTerminology: PackageSearchModule.WithVariants.Terminology, ) : Declared } @@ -104,7 +109,7 @@ sealed interface InfoPanelContent { data class Base( override val packageListId: PackageListItem.Package.Remote.Base.Id, - override val tabTitle: String, + override val tabTitleData: TabTitleData, override val moduleId: PackageSearchModule.Identity, override val title: String, override val subtitle: String, @@ -121,7 +126,7 @@ sealed interface InfoPanelContent { data class WithVariant( override val packageListId: PackageListItem.Package.Remote.WithVariant.Id, - override val tabTitle: String, + override val tabTitleData: TabTitleData, override val moduleId: PackageSearchModule.Identity, override val title: String, override val subtitle: String, @@ -144,18 +149,24 @@ sealed interface InfoPanelContent { sealed interface Attributes : InfoPanelContent { val attributes: List - data class FromVariant( - override val tabTitle: String, + data class FromPackage( + override val attributes: List, + override val tabTitleData: TabTitleData, + ) : Attributes + + data class FromVariantHeader( + override val tabTitleData: TabTitleData, val variantName: String, override val attributes: List, ) : Attributes - data class FromSearch( - override val tabTitle: String, + data class FromSearchHeader( + override val tabTitleData: TabTitleData, override val attributes: List, val defaultSourceSet: String, val additionalSourceSets: List, ) : Attributes } -} \ No newline at end of file +} + diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelViewModel.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelViewModel.kt index 92120490..423d9ac4 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelViewModel.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/InfoPanelViewModel.kt @@ -59,10 +59,10 @@ class InfoPanelViewModel(private val project: Project) : Disposable { event.asPanelContent(onlyStable, isLoading) is InfoPanelContentEvent.Package.Remote.Base -> - event.asPanelContent(isLoading) + event.asPanelContent(onlyStable, isLoading) is InfoPanelContentEvent.Package.Remote.WithVariants -> - event.asPanelContent(isLoading) + event.asPanelContent(onlyStable, isLoading) } } } @@ -88,10 +88,10 @@ class InfoPanelViewModel(private val project: Project) : Disposable { init { combine( - tabs.map { it.map { it.tabTitle } }, + tabs.map { it.map { it.tabTitleData } }, activeTabTitleMutableStateFlow ) { tabTitles, activeTabTitle -> - if (activeTabTitle !in tabTitles) tabTitles.firstOrNull() else activeTabTitle + if (activeTabTitle !in tabTitles.map { it.tabTitle }) tabTitles.firstOrNull()?.tabTitle else activeTabTitle } .mapNotNull { it } .onEach { activeTabTitleMutableStateFlow.emit(it) } diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/asPanelContent.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/asPanelContent.kt index c44c20d0..6073aeab 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/asPanelContent.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/model/infopanel/asPanelContent.kt @@ -3,8 +3,10 @@ package com.jetbrains.packagesearch.plugin.ui.model.infopanel import com.jetbrains.packagesearch.plugin.PackageSearchBundle.message import com.jetbrains.packagesearch.plugin.core.data.IconProvider import com.jetbrains.packagesearch.plugin.core.data.PackageSearchDeclaredPackage +import com.jetbrains.packagesearch.plugin.core.data.listKMPAttributesNames import com.jetbrains.packagesearch.plugin.core.extensions.PackageSearchKnownRepositoriesContext import com.jetbrains.packagesearch.plugin.core.utils.icon +import com.jetbrains.packagesearch.plugin.gradle.buildAttributesFromRawStrings import com.jetbrains.packagesearch.plugin.ui.model.getLatestVersion import org.jetbrains.packagesearch.api.v3.ApiMavenPackage import org.jetbrains.packagesearch.api.v3.ApiPackage @@ -20,40 +22,44 @@ context(PackageSearchKnownRepositoriesContext) internal fun InfoPanelContentEvent.Package.Declared.Base.asPanelContent( onlyStable: Boolean, isLoading: Boolean, -) = listOf( - InfoPanelContent.PackageInfo.Declared.Base( - moduleId = module.identity, - packageListId = packageListId, - tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.overview"), - title = declaredPackage.displayName, - subtitle = declaredPackage.coordinates, - icon = declaredPackage.icon, - type = declaredPackage.typeInfo, - licenses = declaredPackage.remoteInfo?.licenses?.asInfoPanelLicenseList() ?: emptyList(), - authors = declaredPackage.remoteInfo?.authors?.mapNotNull { it.name } ?: emptyList(), - description = declaredPackage.remoteInfo - ?.description - ?.sanitizeDescription(), - scm = declaredPackage.remoteInfo?.scm?.asInfoPanelScm(), - readmeUrl = declaredPackage.remoteInfo?.scm?.readme?.htmlUrl ?: declaredPackage.remoteInfo?.scm?.readmeUrl, - repositories = declaredPackage.remoteInfo?.repositories() ?: emptyList(), - latestVersion = declaredPackage.getLatestVersion(onlyStable)?.versionName, - declaredVersion = declaredPackage.declaredVersion - ?.versionName - ?: message("packagesearch.ui.missingVersion"), - declaredScope = declaredPackage.declaredScope - ?: message("packagesearch.ui.missingScope"), - availableVersions = declaredPackage.remoteInfo - ?.versions - ?.all - ?.filter { if (onlyStable) it.normalizedVersion.isStable else true } - ?.map { it.normalizedVersion.versionName } - ?: emptyList(), - availableScopes = module.availableScopes, - isLoading = isLoading, - allowMissingScope = !module.dependencyMustHaveAScope +) = buildList { + add( + InfoPanelContent.PackageInfo.Declared.Base( + moduleId = module.identity, + packageListId = packageListId, + tabTitleData = InfoPanelContent.TabTitleData(tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.overview")), + title = declaredPackage.displayName, + subtitle = declaredPackage.coordinates, + icon = declaredPackage.icon, + type = declaredPackage.typeInfo, + licenses = declaredPackage.remoteInfo?.licenses?.asInfoPanelLicenseList() ?: emptyList(), + authors = declaredPackage.remoteInfo?.authors?.mapNotNull { it.name } ?: emptyList(), + description = declaredPackage.remoteInfo + ?.description + ?.sanitizeDescription(), + scm = declaredPackage.remoteInfo?.scm?.asInfoPanelScm(), + readmeUrl = declaredPackage.remoteInfo?.scm?.readme?.htmlUrl ?: declaredPackage.remoteInfo?.scm?.readmeUrl, + repositories = declaredPackage.remoteInfo?.repositories() ?: emptyList(), + latestVersion = declaredPackage.getLatestVersion(onlyStable)?.versionName, + declaredVersion = declaredPackage.declaredVersion + ?.versionName + ?: message("packagesearch.ui.missingVersion"), + declaredScope = declaredPackage.declaredScope + ?: message("packagesearch.ui.missingScope"), + availableVersions = declaredPackage.remoteInfo + ?.versions + ?.all + ?.filter { if (onlyStable) it.normalizedVersion.isStable else true } + ?.map { it.normalizedVersion.versionName } + ?: emptyList(), + availableScopes = module.availableScopes, + isLoading = isLoading, + allowMissingScope = !module.dependencyMustHaveAScope + ) ) -) + addAttributesFromNames(declaredPackage.listKMPAttributesNames(onlyStable)) +} + internal val PackageSearchDeclaredPackage.typeInfo: InfoPanelContent.PackageInfo.Type? get() { @@ -91,115 +97,128 @@ context(PackageSearchKnownRepositoriesContext) internal fun InfoPanelContentEvent.Package.Declared.WithVariant.asPanelContent( onlyStable: Boolean, isLoading: Boolean, -) = listOf( - InfoPanelContent.PackageInfo.Declared.WithVariant( - moduleId = module.identity, - packageListId = packageListId, - tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.overview"), - title = declaredPackage.displayName, - subtitle = declaredPackage.coordinates, - icon = declaredPackage.icon, - type = declaredPackage.typeInfo, - licenses = declaredPackage.remoteInfo?.licenses?.asInfoPanelLicenseList() ?: emptyList(), - authors = declaredPackage.remoteInfo?.authors?.mapNotNull { it.name } ?: emptyList(), - description = declaredPackage.remoteInfo - ?.description - ?.sanitizeDescription(), - scm = declaredPackage.remoteInfo?.scm?.asInfoPanelScm(), - readmeUrl = declaredPackage.remoteInfo?.scm?.readme?.htmlUrl ?: declaredPackage.remoteInfo?.scm?.readmeUrl, - repositories = declaredPackage.remoteInfo?.repositories() ?: emptyList(), - latestVersion = declaredPackage.getLatestVersion(onlyStable)?.versionName, - declaredVersion = declaredPackage.declaredVersion - ?.versionName - ?: message("packagesearch.ui.missingVersion"), - declaredScope = declaredPackage.declaredScope - ?: message("packagesearch.ui.missingScope"), - availableVersions = declaredPackage.remoteInfo - ?.versions - ?.all - ?.filter { if (onlyStable) it.normalizedVersion.isStable else true } - ?.map { it.normalizedVersion.versionName } - ?: emptyList(), - availableScopes = module.variants.getValue(variantName).availableScopes, - isLoading = isLoading, - compatibleVariants = module.variants.keys.sorted() - variantName, - declaredVariant = variantName, - allowMissingScope = !module.dependencyMustHaveAScope, - variantTerminology = module.variantTerminology - ), - InfoPanelContent.Attributes.FromVariant( - variantName = variantName, - tabTitle = message("packagesearch.ui.toolwindow.sidepanel.platforms"), - attributes = module.variants.getValue(variantName).attributes +) = buildList { + add( + InfoPanelContent.PackageInfo.Declared.WithVariant( + moduleId = module.identity, + packageListId = packageListId, + tabTitleData = InfoPanelContent.TabTitleData(tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.overview")), + title = declaredPackage.displayName, + subtitle = declaredPackage.coordinates, + icon = declaredPackage.icon, + type = declaredPackage.typeInfo, + licenses = declaredPackage.remoteInfo?.licenses?.asInfoPanelLicenseList() ?: emptyList(), + authors = declaredPackage.remoteInfo?.authors?.mapNotNull { it.name } ?: emptyList(), + description = declaredPackage.remoteInfo + ?.description + ?.sanitizeDescription(), + scm = declaredPackage.remoteInfo?.scm?.asInfoPanelScm(), + readmeUrl = declaredPackage.remoteInfo?.scm?.readme?.htmlUrl ?: declaredPackage.remoteInfo?.scm?.readmeUrl, + repositories = declaredPackage.remoteInfo?.repositories() ?: emptyList(), + latestVersion = declaredPackage.getLatestVersion(onlyStable)?.versionName, + declaredVersion = declaredPackage.declaredVersion + ?.versionName + ?: message("packagesearch.ui.missingVersion"), + declaredScope = declaredPackage.declaredScope + ?: message("packagesearch.ui.missingScope"), + availableVersions = declaredPackage.remoteInfo + ?.versions + ?.all + ?.filter { if (onlyStable) it.normalizedVersion.isStable else true } + ?.map { it.normalizedVersion.versionName } + ?: emptyList(), + availableScopes = module.variants.getValue(variantName).availableScopes, + isLoading = isLoading, + compatibleVariants = module.variants.keys.sorted() - variantName, + declaredVariant = variantName, + allowMissingScope = !module.dependencyMustHaveAScope, + variantTerminology = module.variantTerminology + ) ) -) + addAttributesFromNames(declaredPackage.listKMPAttributesNames(onlyStable)) +} context(PackageSearchKnownRepositoriesContext) internal fun InfoPanelContentEvent.Package.Remote.WithVariants.asPanelContent( + onlyStable: Boolean, isLoading: Boolean, -) = listOf( - InfoPanelContent.PackageInfo.Remote.WithVariant( - tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.overview"), - moduleId = module.identity, - packageListId = packageListId, - title = apiPackage.name, - subtitle = apiPackage.coordinates, - icon = apiPackage.icon, - type = apiPackage.typeInfo, - licenses = apiPackage.licenses?.asInfoPanelLicenseList() ?: emptyList(), - authors = apiPackage.authors.mapNotNull { it.name }, - description = apiPackage.description?.sanitizeDescription(), - scm = apiPackage.scm?.asInfoPanelScm(), - readmeUrl = apiPackage.scm?.readme?.htmlUrl ?: apiPackage.scm?.readmeUrl, - primaryVariant = primaryVariantName, - additionalVariants = compatibleVariantNames.sorted() - primaryVariantName, - repositories = apiPackage.repositories(), - isLoading = isLoading, - isInstalledInPrimaryVariant = module.variants.getValue(primaryVariantName).declaredDependencies - .any { it.id == apiPackage.id } - ), - InfoPanelContent.Attributes.FromVariant( - tabTitle = message("packagesearch.ui.toolwindow.sidepanel.platforms"), - variantName = primaryVariantName, - attributes = module.variants.getValue(primaryVariantName).attributes - ) -) +) = buildList { + add( + InfoPanelContent.PackageInfo.Remote.WithVariant( + tabTitleData = InfoPanelContent.TabTitleData(tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.overview")), + moduleId = module.identity, + packageListId = packageListId, + title = apiPackage.name, + subtitle = apiPackage.coordinates, + icon = apiPackage.icon, + type = apiPackage.typeInfo, + licenses = apiPackage.licenses?.asInfoPanelLicenseList() ?: emptyList(), + authors = apiPackage.authors.mapNotNull { it.name }, + description = apiPackage.description?.sanitizeDescription(), + scm = apiPackage.scm?.asInfoPanelScm(), + readmeUrl = apiPackage.scm?.readme?.htmlUrl ?: apiPackage.scm?.readmeUrl, + primaryVariant = primaryVariantName, + additionalVariants = compatibleVariantNames.sorted() - primaryVariantName, + repositories = apiPackage.repositories(), + isLoading = isLoading, + isInstalledInPrimaryVariant = module.variants.getValue(primaryVariantName).declaredDependencies + .any { it.id == apiPackage.id } + )) + addAttributesFromNames(apiPackage.listKMPAttributesNames(onlyStable)) +} context(PackageSearchKnownRepositoriesContext) internal fun InfoPanelContentEvent.Package.Remote.Base.asPanelContent( + onlyStable: Boolean, isLoading: Boolean, -) = listOf( - InfoPanelContent.PackageInfo.Remote.Base( - tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.overview"), - moduleId = module.identity, - packageListId = packageListId, - title = apiPackage.name, - subtitle = apiPackage.coordinates, - icon = apiPackage.icon, - type = apiPackage.typeInfo, - licenses = apiPackage.licenses?.asInfoPanelLicenseList() ?: emptyList(), - authors = apiPackage.authors.mapNotNull { it.name }, - description = apiPackage.description?.sanitizeDescription(), - scm = apiPackage.scm?.asInfoPanelScm(), - readmeUrl = apiPackage.scm?.readme?.htmlUrl ?: apiPackage.scm?.readmeUrl, - repositories = apiPackage.repositories(), - isLoading = isLoading +) = buildList { + add( + InfoPanelContent.PackageInfo.Remote.Base( + tabTitleData = InfoPanelContent.TabTitleData(tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.overview")), + moduleId = module.identity, + packageListId = packageListId, + title = apiPackage.name, + subtitle = apiPackage.coordinates, + icon = apiPackage.icon, + type = apiPackage.typeInfo, + licenses = apiPackage.licenses?.asInfoPanelLicenseList() ?: emptyList(), + authors = apiPackage.authors.mapNotNull { it.name }, + description = apiPackage.description?.sanitizeDescription(), + scm = apiPackage.scm?.asInfoPanelScm(), + readmeUrl = apiPackage.scm?.readme?.htmlUrl ?: apiPackage.scm?.readmeUrl, + repositories = apiPackage.repositories(), + isLoading = isLoading + ) ) -) + addAttributesFromNames(apiPackage.listKMPAttributesNames(onlyStable)) + + +} internal fun InfoPanelContentEvent.Attributes.FromVariant.asPanelContent() = listOf( - InfoPanelContent.Attributes.FromVariant( + InfoPanelContent.Attributes.FromVariantHeader( + tabTitleData = InfoPanelContent.TabTitleData(tabTitle = message("packagesearch.ui.toolwindow.sidepanel.platforms")), variantName = variantName, - tabTitle = message("packagesearch.ui.toolwindow.sidepanel.platforms"), attributes = attributes, ) ) internal fun InfoPanelContentEvent.Attributes.FromSearch.asPanelContent() = listOf( - InfoPanelContent.Attributes.FromSearch( - tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.attributes"), + InfoPanelContent.Attributes.FromSearchHeader( + tabTitleData = InfoPanelContent.TabTitleData(tabTitle = message("packagesearch.ui.toolwindow.packages.details.info.attributes")), defaultSourceSet = defaultVariant, additionalSourceSets = additionalVariants, attributes = attributes, ) -) \ No newline at end of file +) + +private fun MutableList.addAttributesFromNames( + attributesNames: Set, +) { + if (attributesNames.isNotEmpty()) add( + InfoPanelContent.Attributes.FromPackage( + tabTitleData = InfoPanelContent.TabTitleData(tabTitle = message("packagesearch.ui.toolwindow.sidepanel.platforms")), + attributes = buildAttributesFromRawStrings(attributesNames) + ) + ) +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/HeaderAttributesTab.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/HeaderAttributesTab.kt index 3f8022b7..46f9d458 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/HeaderAttributesTab.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/HeaderAttributesTab.kt @@ -5,7 +5,6 @@ import androidx.compose.animation.core.spring import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width @@ -59,7 +58,7 @@ private fun HeaderAttributesTabImpl( val attributeGlobalPositionMap = remember { mutableMapOf() } Column(verticalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.padding(12.dp)) { - if (content is InfoPanelContent.Attributes.FromSearch) { + if (content is InfoPanelContent.Attributes.FromSearchHeader) { Text( text = contentTitle, fontSize = 14.sp, @@ -76,7 +75,7 @@ private fun HeaderAttributesTabImpl( } } - if (content is InfoPanelContent.Attributes.FromSearch) { + if (content is InfoPanelContent.Attributes.FromSearchHeader) { SourceSetsList(content, sourceSetString) } @@ -86,8 +85,8 @@ private fun HeaderAttributesTabImpl( @Composable -private fun ColumnScope.SourceSetsList( - content: InfoPanelContent.Attributes.FromSearch, +private fun SourceSetsList( + content: InfoPanelContent.Attributes.FromSearchHeader, title: String, ) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { @@ -175,7 +174,7 @@ fun AttributeItem(modifier: Modifier = Modifier, attributeName: String, nestedAt //@Composable //private fun HeaderAttributesPreviewTab() { // val activeTabMock = InfoPanelContent.Attributes.FromVariant( -// tabTitle = "FromVariant", +// tabTitleData = "FromVariant", // variantName = "jvm", // attributes = generateAttributesMock() // ) diff --git a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/PackageSearchInfoPanel.kt b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/PackageSearchInfoPanel.kt index 3df9d83c..d063eaa3 100644 --- a/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/PackageSearchInfoPanel.kt +++ b/plugin/src/main/kotlin/com/jetbrains/packagesearch/plugin/ui/panels/side/PackageSearchInfoPanel.kt @@ -38,7 +38,7 @@ fun PackageSearchInfoPanel( val tabs by viewModel.tabs.collectAsState() val activeTabTitle by viewModel.activeTabTitleFlow.collectAsState() // if you use `by derivedStateOf`, the then will fail - val activeTab = derivedStateOf { tabs.firstOrNull { it.tabTitle == activeTabTitle } }.value + val activeTab = derivedStateOf { tabs.firstOrNull { it.tabTitleData.tabTitle == activeTabTitle } }.value when { tabs.isEmpty() || activeTab == null -> NoTabsAvailable() else -> Column(modifier = Modifier.fillMaxSize()) { @@ -46,14 +46,14 @@ fun PackageSearchInfoPanel( modifier = Modifier.fillMaxWidth(), tabs = tabs.map { infoPanelContent -> TabData.Default( - selected = activeTabTitle == infoPanelContent.tabTitle, + selected = activeTabTitle == infoPanelContent.tabTitleData.tabTitle, closable = false, content = { tabState -> Box(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) { - SimpleTabContent(infoPanelContent.tabTitle, tabState) + SimpleTabContent(infoPanelContent.tabTitleData.tabTitle, tabState) } }, - onClick = { viewModel.setActiveTabTitle(infoPanelContent.tabTitle) }, + onClick = { viewModel.setActiveTabTitle(infoPanelContent.tabTitleData.tabTitle) }, ) } ) @@ -72,14 +72,19 @@ fun PackageSearchInfoPanel( ) } - is InfoPanelContent.Attributes.FromVariant -> { + is InfoPanelContent.Attributes.FromVariantHeader -> { HeaderAttributesTab(content = activeTab, scrollState = viewModel.scrollState) } - is InfoPanelContent.Attributes.FromSearch -> { + is InfoPanelContent.Attributes.FromSearchHeader -> { HeaderAttributesTab(content = activeTab, scrollState = viewModel.scrollState) } + + is InfoPanelContent.Attributes.FromPackage -> HeaderAttributesTab( + content = activeTab, + scrollState = viewModel.scrollState + ) } } VerticalScrollbar( diff --git a/plugin/src/main/resources/messages/packageSearchBundle.properties b/plugin/src/main/resources/messages/packageSearchBundle.properties index 56b0bdf9..2161efa1 100644 --- a/plugin/src/main/resources/messages/packageSearchBundle.properties +++ b/plugin/src/main/resources/messages/packageSearchBundle.properties @@ -225,6 +225,6 @@ packagesearch.toolwindow.loading.dumbMode=Project is initializing... packagesearch.ui.tree.tooltips.expand=Expand all packagesearch.ui.tree.tooltips.collapse=Collapse all packagesearch.ui.toolwindow.sidepanel.platformsList=Platforms: -packagesearch.ui.toolwindow.sidepanel.platforms=Platforms +packagesearch.ui.toolwindow.sidepanel.platforms=Kotlin Platform packagesearch.ui.toolwindow.sidepanel.searchResultSupport=Search results that support: packagesearch.ui.toolwindow.sidepanel.searchResultSupport.sourceSets=Source Sets: