Skip to content

Commit

Permalink
Update 242 IJ Platform to beta 1 (#446)
Browse files Browse the repository at this point in the history
* Bump IJP to 242.20224.38 (242 beta 1)

* Update icon keys generator to use IconPathProvider

This simplifies considerably our code since we do not need to parse the
json mappings anymore.

* Update AllIconsKeys with new icons

* Fix icon keys generator to handle weird partial paths
  • Loading branch information
rock3r authored Jul 15, 2024
1 parent 995f263 commit e3fa051
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 100 deletions.
111 changes: 31 additions & 80 deletions buildSrc/src/main/kotlin/icon-keys-generator.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}")

Expand All @@ -146,71 +128,35 @@ open class IconKeysGeneratorTask : DefaultTask() {
)
}

private fun readIconMappingJson(rawMapping: JsonElement): Map<String, String> {
val flattenedMappings = mutableMapOf<String, Set<String>>()

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<String, Set<String>>,
) {
when (node) {
is JsonPrimitive -> {
if (!node.isString) return
map[oldUiPath] = setOf(node.content)
}

is JsonArray -> {
map[oldUiPath] =
node
.filterIsInstance<JsonPrimitive>()
.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<Boolean>()
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<String, String>,
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 }

Expand All @@ -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 }
}
Expand Down Expand Up @@ -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 }
}
}

Expand Down
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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 =
Expand Down
18 changes: 6 additions & 12 deletions ui/api/ui.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
)
}
Expand Down

0 comments on commit e3fa051

Please sign in to comment.