diff --git a/buildSrc/src/main/kotlin/icon-keys-generator.gradle.kts b/buildSrc/src/main/kotlin/icon-keys-generator.gradle.kts index bd4d6e0c0..288929925 100644 --- a/buildSrc/src/main/kotlin/icon-keys-generator.gradle.kts +++ b/buildSrc/src/main/kotlin/icon-keys-generator.gradle.kts @@ -5,12 +5,6 @@ import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec import io.gitlab.arturbosch.detekt.Detekt -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonNull -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.gradle.tasks.BaseKotlinCompile @@ -103,27 +97,15 @@ open class IconKeysGeneratorTask : DefaultTask() { "Is the correct dependency declared on the iconGeneration configuration?" ) - // Step 1) load icon mappings from JSON - val mappingsJsonBytes = - classLoader.getResourceAsStream("PlatformIconMappings.json") - ?.use { it.readAllBytes() } - ?: error("Icon mapping JSON not found") - - val iconMappingJson = - json.parseToJsonElement(mappingsJsonBytes.decodeToString()) - - // Step 2) Transform mappings to a map oldPath -> newPath - val iconMapping = readIconMappingJson(iconMappingJson) - logger.lifecycle("Icon mapping JSON read. It has ${iconMapping.size} entries") - - // Step 3) Traverse sourceClass by using reflection, collecting all members + // Traverse sourceClass by using reflection, collecting all members // This step uses the mappings to add the new paths where available. val dummyIconClass = classLoader.loadClass("com.intellij.ui.DummyIconImpl") - val pathField = dummyIconClass.getPathField() + val oldUiPathField = dummyIconClass.getOriginalPathField() + val newUiPathField = dummyIconClass.getNewUiPathField() val rootHolder = IconKeyHolder(sourceClass.simpleName) - pathField.whileForcingAccessible { - visitSourceClass(sourceClass, iconMapping, rootHolder, pathField, classLoader) + whileForcingAccessible(oldUiPathField, newUiPathField) { + visitSourceClass(sourceClass, rootHolder, oldUiPathField, newUiPathField, classLoader) } logger.lifecycle("Read icon keys from ${sourceClass.name}") @@ -146,71 +128,35 @@ open class IconKeysGeneratorTask : DefaultTask() { ) } - private fun readIconMappingJson(rawMapping: JsonElement): Map { - val flattenedMappings = mutableMapOf>() - - visitMapping(oldUiPath = "", node = rawMapping, map = flattenedMappings) - - return flattenedMappings - .flatMap { (newPath, oldPaths) -> - oldPaths.map { oldPath -> oldPath to newPath } - }.toMap() - } - - private fun visitMapping( - oldUiPath: String, - node: JsonElement, - map: MutableMap>, - ) { - when (node) { - is JsonPrimitive -> { - if (!node.isString) return - map[oldUiPath] = setOf(node.content) - } - - is JsonArray -> { - map[oldUiPath] = - node - .filterIsInstance() - .filter { child -> child.isString } - .map { it.content } - .toSet() - } - - is JsonObject -> { - for ((key, value) in node.entries) { - val childOldPath = if (oldUiPath.isNotEmpty()) "$oldUiPath/$key" else key - visitMapping(oldUiPath = childOldPath, node = value, map = map) - } - } - - JsonNull -> error("Null nodes not supported") + private fun whileForcingAccessible(vararg fields: Field, action: () -> Unit) { + val wasAccessibles = mutableListOf() + for (field in fields) { + @Suppress("DEPRECATION") + wasAccessibles += field.isAccessible + field.isAccessible = true } - } - private fun Field.whileForcingAccessible(action: () -> Unit) { - @Suppress("DEPRECATION") - val wasAccessible = isAccessible - isAccessible = true try { action() } finally { - isAccessible = wasAccessible + for ((index, field) in fields.withIndex()) { + field.isAccessible = wasAccessibles[index] + } } } private fun visitSourceClass( sourceClass: Class<*>, - iconMappings: Map, parentHolder: IconKeyHolder, - pathField: Field, + oldUiPathField: Field, + newUiPathField: Field, classLoader: ClassLoader, ) { for (child in sourceClass.declaredClasses) { val childName = "${parentHolder.name}.${child.simpleName}" val childHolder = IconKeyHolder(childName) parentHolder.holders += childHolder - visitSourceClass(child, iconMappings, childHolder, pathField, classLoader) + visitSourceClass(child, childHolder, oldUiPathField, newUiPathField, classLoader) } parentHolder.holders.sortBy { it.name } @@ -225,12 +171,18 @@ open class IconKeysGeneratorTask : DefaultTask() { } val icon = field.get(sourceClass) - val oldPath = pathField.get(icon) as String - - val newPath = iconMappings[oldPath] - validatePath(oldPath, fieldName, classLoader) - newPath?.let { validatePath(it, fieldName, classLoader) } - parentHolder.keys += IconKey(fieldName, oldPath, newPath) + val oldUiPath = oldUiPathField.get(icon) as String? + ?: throw GradleException("Found null path in icon $fieldName") + validatePath(oldUiPath, fieldName, classLoader) + + // New UI paths may be "partial", meaning they end with a / character. + // In this case, we're supposed to append the old UI path to the new UI + // path, because that's just how they decided to encode things in IJP. + val newUiPath = (newUiPathField.get(icon) as String?) + ?.let { if (it.endsWith("/")) it + oldUiPath else it } + ?: oldUiPath + validatePath(newUiPath, fieldName, classLoader) + parentHolder.keys += IconKey(fieldName, oldUiPath, newUiPath) } parentHolder.keys.sortBy { it.name } } @@ -309,11 +261,10 @@ open class IconKeysGeneratorTask : DefaultTask() { companion object { - private fun Class<*>.getPathField(): Field = declaredFields.first { it.name == "path" } + private fun Class<*>.getOriginalPathField(): Field = declaredFields.first { it.name == "originalPath" } + private fun Class<*>.getNewUiPathField(): Field = declaredFields.first { it.name == "expUIPath" } private val keyClassName = ClassName("org.jetbrains.jewel.ui.icon", "IntelliJIconKey") - - private val json = Json { isLenient = true } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index db5dcaaff..77985e8fd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,9 +3,9 @@ commonmark = "0.22.0" composeDesktop = "1.7.0-dev1703" detekt = "1.23.6" dokka = "1.9.20" -idea = "242.19890.14" -intelliJPlatformBuild = "242.19890.14-EAP-SNAPSHOT" -ideaPlugin = "2.0.0-beta8" +idea = "242.20224.38" +intelliJPlatformBuild = "242.20224.38-EAP-SNAPSHOT" +ideaPlugin = "2.0.0-beta9" jna = "5.14.0" kotlin = "1.9.24" kotlinSarif = "0.5.0" diff --git a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/icon/IntelliJIconKey.kt b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/icon/IntelliJIconKey.kt index f565e9ff8..667cb8245 100644 --- a/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/icon/IntelliJIconKey.kt +++ b/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/icon/IntelliJIconKey.kt @@ -1,13 +1,12 @@ package org.jetbrains.jewel.bridge.icon -import com.intellij.ui.icons.CachedImageIcon +import com.intellij.ui.icons.IconPathProvider import org.jetbrains.jewel.ui.icon.IconKey import org.jetbrains.jewel.ui.icon.IntelliJIconKey -@Suppress("UnstableApiUsage") // We need to use internal APIs public fun IntelliJIconKey.Companion.fromPlatformIcon(icon: javax.swing.Icon): IconKey { - check(icon is CachedImageIcon) { - "Only resource-backed CachedImageIcons are supported (e.g., coming from AllIcons)" + check(icon is IconPathProvider) { + "Only icons implementing IconPathsProvider are supported (e.g., coming from AllIcons)" } val oldUiPath = diff --git a/ui/api/ui.api b/ui/api/ui.api index 64080a216..9d82334a0 100644 --- a/ui/api/ui.api +++ b/ui/api/ui.api @@ -3438,31 +3438,24 @@ public final class org/jetbrains/jewel/ui/icons/AllIconsKeys$ToolbarDecorator { public final fun getImport ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; } -public final class org/jetbrains/jewel/ui/icons/AllIconsKeys$Toolwindow { - public static final field $stable I - public static final field INSTANCE Lorg/jetbrains/jewel/ui/icons/AllIconsKeys$Toolwindow; - public final fun getChanges ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; - public final fun getDependencies ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; - public final fun getMeetNewUi ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; - public final fun getRepositories ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; - public final fun getTask ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; - public final fun getToolWindowDuplicates ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; - public final fun getToolWindowInternal ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; -} - public final class org/jetbrains/jewel/ui/icons/AllIconsKeys$Toolwindows { public static final field $stable I public static final field INSTANCE Lorg/jetbrains/jewel/ui/icons/AllIconsKeys$Toolwindows; + public final fun getChanges ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; + public final fun getDependencies ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getDocumentation ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getErrorEvents ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getInfoEvents ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; + public final fun getMeetNewUi ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getNoEvents ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getNotifications ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getNotificationsNew ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getNotificationsNewImportant ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getProblems ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getProblemsEmpty ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; + public final fun getRepositories ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getSettingSync ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; + public final fun getTask ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getToolWindowAnalyzeDataflow ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getToolWindowAnt ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getToolWindowBookmarks ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; @@ -3473,6 +3466,7 @@ public final class org/jetbrains/jewel/ui/icons/AllIconsKeys$Toolwindows { public final fun getToolWindowCoverage ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getToolWindowDataView ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getToolWindowDebugger ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; + public final fun getToolWindowDuplicates ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getToolWindowFavorites ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getToolWindowFind ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; public final fun getToolWindowHierarchy ()Lorg/jetbrains/jewel/ui/icon/IntelliJIconKey; diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Tabs.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Tabs.kt index 309bd3a0a..6f110d7a9 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Tabs.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Tabs.kt @@ -83,7 +83,7 @@ public fun TabContentScope.SimpleTabContent( SimpleTabContent( state = state, modifier = modifier, - icon = iconKey?.let { { Icon(key = iconKey, contentDescription = null, hints = *painterHints) } }, + icon = iconKey?.let { { Icon(key = iconKey, contentDescription = null, hints = painterHints) } }, label = { Text(label) }, ) }