[jewel] IJPL-173264 Migrate Jewel icon keys generation under Icons generation script

(cherry picked from commit c2e29e9e8bfa940b3317bb9f428c6506475b502f)

GitOrigin-RevId: 1c347ad240e93f1c588a1bd5569bac9f6e1ce46c
This commit is contained in:
Jakub Senohrabek
2025-02-25 14:41:17 +01:00
committed by intellij-monorepo-bot
parent 766beca004
commit 3bccea31f2
5 changed files with 1736 additions and 4355 deletions

View File

@@ -28,13 +28,14 @@ import java.util.*
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.xml.stream.XMLStreamException import javax.xml.stream.XMLStreamException
import kotlin.collections.any
import kotlin.io.path.exists import kotlin.io.path.exists
@JvmRecord @JvmRecord
internal data class ModifiedClass( internal data class ModifiedClass(
@JvmField val module: JpsModule, @JvmField val module: JpsModule,
@JvmField val file: Path, @JvmField val file: Path,
@JvmField val result: CharSequence @JvmField val result: CharSequence,
) )
@JvmRecord @JvmRecord
@@ -42,15 +43,17 @@ internal data class IconClassInfo(
@JvmField val packageName: String, @JvmField val packageName: String,
@JvmField val className: String, @JvmField val className: String,
@JvmField val outFile: Path, @JvmField val outFile: Path,
@JvmField val jewelOutFile: Path,
@JvmField val images: Collection<ImageInfo>, @JvmField val images: Collection<ImageInfo>,
@JvmField val mappings: Map<String, String>? = null, @JvmField val mappings: Map<String, String>? = null,
@JvmField val isInternal: Boolean = false, @JvmField val isInternal: Boolean = false,
@JvmField val jewelPackageName: String? = null,
) )
internal open class IconsClassGenerator( internal open class IconsClassGenerator(
private val projectHome: Path, private val projectHome: Path,
@JvmField val modules: List<JpsModule>, @JvmField val modules: List<JpsModule>,
private val writeChangesToDisk: Boolean = true private val writeChangesToDisk: Boolean = true,
) { ) {
private companion object { private companion object {
private const val ICON_MANAGER_CODE = "IconManager.getInstance()" private const val ICON_MANAGER_CODE = "IconManager.getInstance()"
@@ -92,26 +95,38 @@ internal open class IconsClassGenerator(
modules.find { it.name == "intellij.platform.util.ui" } ?: error("Can't load module 'util'") modules.find { it.name == "intellij.platform.util.ui" } ?: error("Can't load module 'util'")
} }
private val jewelUiModule: JpsModule by lazy {
modules.find { it.name == "intellij.platform.jewel.ui" } ?: error("Can't load module 'Jewel UI'")
}
private val androidArtworkComposeModule: JpsModule by lazy {
modules.find { it.name == "intellij.android.artwork-compose" } ?: error("Can't load module 'android.artwork-compose'")
}
internal open fun getIconClassInfo(module: JpsModule, moduleConfig: IntellijIconClassGeneratorModuleConfig?): List<IconClassInfo> { internal open fun getIconClassInfo(module: JpsModule, moduleConfig: IntellijIconClassGeneratorModuleConfig?): List<IconClassInfo> {
when (module.name) { when (module.name) {
"intellij.platform.icons" -> { "intellij.platform.icons" -> {
val packageName = "com.intellij.icons" val packageName = "com.intellij.icons"
val jewelPackageName = "org.jetbrains.jewel.ui.icons"
val className = "AllIcons" val className = "AllIcons"
val dir = utilUi.getSourceRoots(JavaSourceRootType.SOURCE).first().path.toAbsolutePath().resolve("com/intellij/icons") val dir = utilUi.getSourceRoots(JavaSourceRootType.SOURCE).first().path.toAbsolutePath().resolve("com/intellij/icons")
val outFile = dir.resolve("$className.java") val outFile = dir.resolve("$className.java")
val jewelDir = jewelUiModule.getSourceRoots(JavaSourceRootType.SOURCE).first { it.path.toString().contains("generated") }.path.toAbsolutePath().resolve(jewelPackageName.replace(".", "/"))
val jewelOutFile = jewelDir.resolve("${className}Keys.java")
val imageCollector = ImageCollector(projectHome = projectHome, moduleConfig = moduleConfig) val imageCollector = ImageCollector(projectHome = projectHome, moduleConfig = moduleConfig)
val images = imageCollector.collect(module = module, includePhantom = true) val images = imageCollector.collect(module = module, includePhantom = true)
imageCollector.printUsedIconRobots() imageCollector.printUsedIconRobots()
val (allImages, mappings) = imageCollector.mergeImages(images, module) val (allImages, mappings) = imageCollector.mergeImages(images, module)
return listOf(IconClassInfo(packageName = packageName, className = className, outFile = outFile, images = allImages, mappings = mappings)) return listOf(IconClassInfo(packageName = packageName, className = className, outFile = outFile, jewelOutFile = jewelOutFile, images = allImages, mappings = mappings, jewelPackageName = jewelPackageName))
} }
"intellij.android.artwork" -> { "intellij.android.artwork" -> {
val packageName = "icons" val packageName = "icons"
val sourceRoot = module.getSourceRoots(JavaSourceRootType.SOURCE).single().file.absolutePath val sourceRoot = module.getSourceRoots(JavaSourceRootType.SOURCE).single().file.absolutePath
val composeSourceRoot = androidArtworkComposeModule.getSourceRoots(JavaSourceRootType.SOURCE).single().file.absolutePath
val resourceRoot = module.getSourceRoots(JavaResourceRootType.RESOURCE).single() val resourceRoot = module.getSourceRoots(JavaResourceRootType.RESOURCE).single()
// avoid a merge conflict - do not transform StudioIcons to a nested class of AndroidIcons // avoid a merge conflict - do not transform StudioIcons to a nested class of AndroidIcons
var imageCollector = ImageCollector(projectHome, moduleConfig = moduleConfig) var imageCollector = ImageCollector(projectHome, moduleConfig = moduleConfig)
@@ -128,9 +143,9 @@ internal open class IconsClassGenerator(
val (studioImages, studioMappings) = imageCollector.mergeImages(imagesS, module) val (studioImages, studioMappings) = imageCollector.mergeImages(imagesS, module)
return listOf( return listOf(
IconClassInfo(packageName, "AndroidIcons", Path.of(sourceRoot, "icons/AndroidIcons.java"), imagesA), IconClassInfo(packageName, "AndroidIcons", Path.of(sourceRoot, "icons/AndroidIcons.java"), Path.of(composeSourceRoot, "icons/AndroidIconsCompose.java"), imagesA),
IconClassInfo(packageName, "StudioIcons", Path.of(sourceRoot, "icons/StudioIcons.java"), studioImages, studioMappings), IconClassInfo(packageName, "StudioIcons", Path.of(sourceRoot, "icons/StudioIcons.java"), Path.of(composeSourceRoot, "icons/StudioIconsCompose.java"), studioImages, studioMappings),
IconClassInfo(packageName, "StudioIllustrations", Path.of(sourceRoot, "icons/StudioIllustrations.java"), imagesI), IconClassInfo(packageName, "StudioIllustrations", Path.of(sourceRoot, "icons/StudioIllustrations.java"), Path.of(composeSourceRoot, "icons/StudioIllustrationsCompose.java"), imagesI),
) )
} }
else -> { else -> {
@@ -163,8 +178,9 @@ internal open class IconsClassGenerator(
?: existingIconsClass?.className ?: existingIconsClass?.className
?: "${directoryName(module).removeSuffix("Icons")}Icons" ?: "${directoryName(module).removeSuffix("Icons")}Icons"
val outFile = targetRoot.resolve("$className.java") val outFile = targetRoot.resolve("$className.java")
val jewelOutFile = targetRoot.resolve("${className}Keys.java")
val (allImages, mappings) = imageCollector.mergeImages(images, module) val (allImages, mappings) = imageCollector.mergeImages(images, module)
val info = IconClassInfo(packageName = packageName, className = className, outFile = outFile, images = allImages, mappings = mappings, isInternal = className.contains("Impl")) val info = IconClassInfo(packageName = packageName, className = className, outFile = outFile, jewelOutFile = jewelOutFile, images = allImages, mappings = mappings, isInternal = className.contains("Impl"))
return transformIconClassInfo(info, module) return transformIconClassInfo(info, module)
} }
} }
@@ -181,12 +197,12 @@ internal open class IconsClassGenerator(
val sourceRoot: JpsTypedModuleSourceRoot<JavaSourceRootProperties>, val sourceRoot: JpsTypedModuleSourceRoot<JavaSourceRootProperties>,
val filePath: Path, val filePath: Path,
val packageName: String, val packageName: String,
val className: String val className: String,
) )
private fun findExistingIconsClass( private fun findExistingIconsClass(
sourceRoots: List<JpsTypedModuleSourceRoot<JavaSourceRootProperties>>, sourceRoots: List<JpsTypedModuleSourceRoot<JavaSourceRootProperties>>,
possiblePackageNames: List<String> possiblePackageNames: List<String>,
): ExistingIconsClass? { ): ExistingIconsClass? {
for (sourceRoot in sourceRoots) { for (sourceRoot in sourceRoots) {
for (packageName in possiblePackageNames) { for (packageName in possiblePackageNames) {
@@ -208,9 +224,48 @@ internal open class IconsClassGenerator(
} }
fun processModule(module: JpsModule, moduleConfig: IntellijIconClassGeneratorModuleConfig?) { fun processModule(module: JpsModule, moduleConfig: IntellijIconClassGeneratorModuleConfig?) {
val iconClassesInfo = getIconClassInfo(module, moduleConfig)
processModuleClasses(module, moduleConfig, iconClassesInfo, SwingIconClassSpecificsGenerator)
if (moduleConfig?.generateJewelIcons == true) {
processModuleClasses(module, moduleConfig, iconClassesInfo, JewelIconClassSpecificsGenerator)
}
}
fun printStats() {
println(
"\nGenerated classes: ${processedClasses.get()}. " +
"Processed icons: ${processedIcons.get()}. " +
"Phantom icons: ${processedPhantom.get()}"
)
if (obsoleteClasses.isNotEmpty()) {
println("\nObsolete classes:")
println(obsoleteClasses.joinToString("\n"))
println("\nObsolete class is an icon class that cannot be found anymore. Possible reasons:")
println(
"1. Icons not located under resources root." +
"\n Solution - move icons to resources root or fix existing root type (must be \"resources\")"
)
println("2. Icons were removed but not class.\n Solution - remove class.")
println(
"3. Icons located under resources root named \"compatibilityResources\". \"compatibilityResources\" for icons that not used externally as icon class fields, " +
"but maybe referenced directly by path.\n Solution - remove class or move icons to another resources root"
)
}
}
fun getModifiedClasses(): List<ModifiedClass> = modifiedClasses
private fun processModuleClasses(
module: JpsModule,
moduleConfig: IntellijIconClassGeneratorModuleConfig?,
iconClassesInfo: List<IconClassInfo>,
iconClassSpecificsGenerator: IconClassSpecificsGenerator
) {
val classCode = StringBuilder() val classCode = StringBuilder()
for (iconClassInfo in getIconClassInfo(module, moduleConfig)) { for (iconClassInfo in iconClassesInfo) {
val outFile = iconClassInfo.outFile val outFile = iconClassSpecificsGenerator.pickOutFile(iconClassInfo)
val oldText = try { val oldText = try {
Files.readString(outFile) Files.readString(outFile)
} }
@@ -219,7 +274,7 @@ internal open class IconsClassGenerator(
} }
classCode.setLength(0) classCode.setLength(0)
val newText = writeClass(copyrightComment = getCopyrightComment(oldText, module), info = iconClassInfo, result = classCode) val newText = writeClass(copyrightComment = getCopyrightComment(oldText, module), info = iconClassInfo, result = classCode, iconClassSpecificsGenerator)
if (newText.isNullOrEmpty()) { if (newText.isNullOrEmpty()) {
if (Files.exists(outFile)) { if (Files.exists(outFile)) {
obsoleteClasses.add(outFile) obsoleteClasses.add(outFile)
@@ -248,30 +303,6 @@ internal open class IconsClassGenerator(
} }
} }
fun printStats() {
println(
"\nGenerated classes: ${processedClasses.get()}. " +
"Processed icons: ${processedIcons.get()}. " +
"Phantom icons: ${processedPhantom.get()}"
)
if (obsoleteClasses.isNotEmpty()) {
println("\nObsolete classes:")
println(obsoleteClasses.joinToString("\n"))
println("\nObsolete class is an icon class that cannot be found anymore. Possible reasons:")
println(
"1. Icons not located under resources root." +
"\n Solution - move icons to resources root or fix existing root type (must be \"resources\")"
)
println("2. Icons were removed but not class.\n Solution - remove class.")
println(
"3. Icons located under resources root named \"compatibilityResources\". \"compatibilityResources\" for icons that not used externally as icon class fields, " +
"but maybe referenced directly by path.\n Solution - remove class or move icons to another resources root"
)
}
}
fun getModifiedClasses(): List<ModifiedClass> = modifiedClasses
private fun findIconClass(dir: Path): String? { private fun findIconClass(dir: Path): String? {
dir.directoryStreamIfExists { stream -> dir.directoryStreamIfExists { stream ->
for (it in stream) { for (it in stream) {
@@ -303,18 +334,20 @@ internal open class IconsClassGenerator(
private fun getSeparators(text: String?): LineSeparator = private fun getSeparators(text: String?): LineSeparator =
text?.let { StringUtil.detectSeparators(text) } ?: LineSeparator.LF text?.let { StringUtil.detectSeparators(text) } ?: LineSeparator.LF
private fun writeClass(copyrightComment: String, info: IconClassInfo, result: StringBuilder): CharSequence? { private fun writeClass(
copyrightComment: String,
info: IconClassInfo,
result: StringBuilder,
iconClassSpecificsGenerator: IconClassSpecificsGenerator,
): CharSequence? {
val images = info.images val images = info.images
if (images.isEmpty()) { if (images.isEmpty()) {
return null return null
} }
result.append(copyrightComment) result.append(copyrightComment)
append(result, "package ${info.packageName};\n", 0) append(result, "package ${iconClassSpecificsGenerator.pickPackageName(info)};\n", 0)
append(result, "import com.intellij.ui.IconManager;", 0) iconClassSpecificsGenerator.appendCustomImports(this, result)
append(result, "import org.jetbrains.annotations.NotNull;", 0)
result.append('\n')
append(result, "import javax.swing.*;", 0)
result.append('\n') result.append('\n')
if (images.any(ImageInfo::scheduledForRemoval)) { if (images.any(ImageInfo::scheduledForRemoval)) {
append(result, "import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval;", 0) append(result, "import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval;", 0)
@@ -331,35 +364,19 @@ internal open class IconsClassGenerator(
result.append("@org.jetbrains.annotations.ApiStatus.Internal\n") result.append("@org.jetbrains.annotations.ApiStatus.Internal\n")
} }
val realClassName = iconClassSpecificsGenerator.amendIconClassName(info.className)
result.append("public") result.append("public")
// backward compatibility // backward compatibility
if (info.className != "AllIcons") { if (iconClassSpecificsGenerator.shouldClassBeFinal(info)) {
result.append(" final") result.append(" final")
} }
result.append(" class ").append(info.className).append(" {\n") result.append(" class ").append(realClassName).append(" {\n")
if (info.mappings.isNullOrEmpty() || info.images.find { !info.mappings.containsKey(it.sourceCodeParameterName) } != null) { iconClassSpecificsGenerator.appendTopLevelStatements(this, result, info, images)
append(result, "private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) {", 1)
append(result, "return $ICON_MANAGER_CODE.loadRasterizedIcon(path, ${info.className}.class.getClassLoader(), cacheKey, flags);", 2)
append(result, "}", 1)
}
if (!info.mappings.isNullOrEmpty() && info.images.find { info.mappings.containsKey(it.sourceCodeParameterName) } != null) {
append(result, "private static @NotNull Icon load(@NotNull String expUIPath, @NotNull String path, int cacheKey, int flags) {", 1)
append(result, "return $ICON_MANAGER_CODE.loadRasterizedIcon(path, expUIPath, ${info.className}.class.getClassLoader(), cacheKey, flags);", 2)
append(result, "}", 1)
}
val customExternalLoad = images.any { it.deprecation?.replacementContextClazz != null }
if (customExternalLoad) {
result.append('\n')
append(result, "private static @NotNull Icon load(@NotNull String path, @NotNull Class<?> clazz) {", 1)
append(result, "return $ICON_MANAGER_CODE.getIcon(path, clazz);", 2)
append(result, "}", 1)
}
val inners = StringBuilder() val inners = StringBuilder()
processIcons(images, info.mappings, inners, depth = 0) processIcons(images, info.mappings, inners, depth = 0, iconClassSpecificsGenerator, realClassName)
if (inners.isEmpty()) { if (inners.isEmpty()) {
return null return null
} }
@@ -369,7 +386,14 @@ internal open class IconsClassGenerator(
return result return result
} }
private fun processIcons(images: Collection<ImageInfo>, mappings: Map<String, String>?, result: StringBuilder, depth: Int) { private fun processIcons(
images: Collection<ImageInfo>,
mappings: Map<String, String>?,
result: StringBuilder,
depth: Int,
iconClassSpecificsGenerator: IconClassSpecificsGenerator,
topLevelClass: String
) {
val level = depth + 1 val level = depth + 1
val nodeMap = HashMap<String, MutableList<ImageInfo>>(images.size / 2) val nodeMap = HashMap<String, MutableList<ImageInfo>>(images.size / 2)
@@ -404,7 +428,7 @@ internal open class IconsClassGenerator(
val oldLength = result.length val oldLength = result.length
val className = className(key) val className = className(key)
if (isInlineClass(className)) { if (isInlineClass(className)) {
processIcons(group, mappings, result, depth + 1) processIcons(group, mappings, result, depth + 1, iconClassSpecificsGenerator, topLevelClass)
} }
else { else {
// if first in block, do not add yet another extra newline // if first in block, do not add yet another extra newline
@@ -413,7 +437,7 @@ internal open class IconsClassGenerator(
} }
append(result, "public static final class $className {", level) append(result, "public static final class $className {", level)
val lengthBeforeBody = result.length val lengthBeforeBody = result.length
processIcons(group, mappings, result, depth + 1) processIcons(group, mappings, result, depth + 1, iconClassSpecificsGenerator, topLevelClass)
if (lengthBeforeBody == result.length) { if (lengthBeforeBody == result.length) {
result.setLength(oldLength) result.setLength(oldLength)
} }
@@ -430,7 +454,7 @@ internal open class IconsClassGenerator(
innerClassWasBefore = false innerClassWasBefore = false
result.append('\n') result.append('\n')
} }
appendImage(image, mappings, result, level, hasher) appendImage(image, mappings, result, level, hasher, iconClassSpecificsGenerator, topLevelClass)
} }
} }
} }
@@ -438,7 +462,15 @@ internal open class IconsClassGenerator(
protected open fun isInlineClass(name: CharSequence): Boolean = protected open fun isInlineClass(name: CharSequence): Boolean =
DotnetIconClasses.isInlineClass(name) DotnetIconClasses.isInlineClass(name)
private fun appendImage(image: ImageInfo, mappings: Map<String, String>?, result: StringBuilder, level: Int, hasher: IconHasher) { private fun appendImage(
image: ImageInfo,
mappings: Map<String, String>?,
result: StringBuilder,
level: Int,
hasher: IconHasher,
iconClassSpecificsGenerator: IconClassSpecificsGenerator,
topLevelClass: String
) {
val file = image.basicFile ?: return val file = image.basicFile ?: return
if (!image.phantom && !isIcon(file)) { if (!image.phantom && !isIcon(file)) {
return return
@@ -472,12 +504,11 @@ internal open class IconsClassGenerator(
val deprecation = image.deprecation val deprecation = image.deprecation
if (deprecation?.replacementContextClazz != null) { if (deprecation?.replacementContextClazz != null) {
append(result, "public static final @NotNull Icon $iconName = " + iconClassSpecificsGenerator.appendDeprecationReplacementClass(this, result, iconName, deprecation, level)
"load(\"${deprecation.replacement}\", ${deprecation.replacementContextClazz}.class);", level)
return return
} }
else if (deprecation?.replacementReference != null) { else if (deprecation?.replacementReference != null) {
append(result, "public static final @NotNull Icon $iconName = ${deprecation.replacementReference};", level) iconClassSpecificsGenerator.appendDeprecationReplacementReferenceClass(this, result, iconName, deprecation, level)
return return
} }
@@ -518,13 +549,11 @@ internal open class IconsClassGenerator(
key = 0 key = 0
} }
val imagePathCodeParameter = image.sourceCodeParameterName iconClassSpecificsGenerator.appendIconProperty(this, result, topLevelClass, javaDoc, image, iconName, key, mappings, level)
append(result, "${javaDoc}public static final @NotNull Icon $iconName = " +
"load(${appendExpUIPath(imagePathCodeParameter, mappings)}\"$imagePathCodeParameter\", $key, ${image.getFlags()});", level)
val oldName = deprecatedIconFieldNameMap[iconName] val oldName = deprecatedIconFieldNameMap[iconName]
if (oldName != null) { if (oldName != null) {
append(result, "${javaDoc}public static final @Deprecated @NotNull Icon $oldName = $iconName;", level) iconClassSpecificsGenerator.appendDeprecatedIconPropertyMapping(this, result, javaDoc, oldName, iconName, level)
} }
} }
@@ -740,6 +769,125 @@ internal open class IconsClassGenerator(
} }
return null return null
} }
private interface IconClassSpecificsGenerator {
fun amendIconClassName(string: String): String
fun pickOutFile(iconClassInfo: IconClassInfo): Path
fun pickPackageName(iconClassInfo: IconClassInfo): String?
fun shouldClassBeFinal(iconClassInfo: IconClassInfo): Boolean
fun appendTopLevelStatements(generator: IconsClassGenerator, result: StringBuilder, info: IconClassInfo, images: Collection<ImageInfo>)
fun appendCustomImports(generator: IconsClassGenerator, result: StringBuilder)
fun appendDeprecatedIconPropertyMapping(generator: IconsClassGenerator, result: StringBuilder, javaDoc: String, oldName: String, iconName: CharSequence, level: Int)
fun appendIconProperty(generator: IconsClassGenerator, result: StringBuilder, topLevelClass: String, javaDoc: String, image: ImageInfo, iconName: CharSequence, key: Int, mappings: Map<String, String>?, level: Int)
fun appendDeprecationReplacementClass(generator: IconsClassGenerator, result: StringBuilder, iconName: CharSequence, deprecation: DeprecationData, level: Int)
fun appendDeprecationReplacementReferenceClass(generator: IconsClassGenerator, result: StringBuilder, iconName: CharSequence, deprecation: DeprecationData, level: Int)
}
private object SwingIconClassSpecificsGenerator : IconClassSpecificsGenerator {
override fun amendIconClassName(originalName: String): String = originalName
override fun pickOutFile(iconClassInfo: IconClassInfo): Path = iconClassInfo.outFile
override fun pickPackageName(iconClassInfo: IconClassInfo): String? = iconClassInfo.packageName
override fun shouldClassBeFinal(iconClassInfo: IconClassInfo): Boolean = iconClassInfo.className != "AllIcons"
override fun appendCustomImports(generator: IconsClassGenerator, result: StringBuilder) {
generator.append(result, "import com.intellij.ui.IconManager;", 0)
generator.append(result, "import org.jetbrains.annotations.NotNull;", 0)
result.append('\n')
generator.append(result, "import javax.swing.*;", 0)
}
override fun appendTopLevelStatements(generator: IconsClassGenerator, result: StringBuilder, info: IconClassInfo, images: Collection<ImageInfo>) {
if (info.mappings.isNullOrEmpty() || info.images.find { !info.mappings.containsKey(it.sourceCodeParameterName) } != null) {
generator.append(result, "private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) {", 1)
generator.append(result, "return $ICON_MANAGER_CODE.loadRasterizedIcon(path, ${info.className}.class.getClassLoader(), cacheKey, flags);", 2)
generator.append(result, "}", 1)
}
if (!info.mappings.isNullOrEmpty() && info.images.find { info.mappings.containsKey(it.sourceCodeParameterName) } != null) {
generator.append(result, "private static @NotNull Icon load(@NotNull String expUIPath, @NotNull String path, int cacheKey, int flags) {", 1)
generator.append(result, "return $ICON_MANAGER_CODE.loadRasterizedIcon(path, expUIPath, ${info.className}.class.getClassLoader(), cacheKey, flags);", 2)
generator.append(result, "}", 1)
}
val customExternalLoad = images.any { it.deprecation?.replacementContextClazz != null }
if (customExternalLoad) {
result.append('\n')
generator.append(result, "private static @NotNull Icon load(@NotNull String path, @NotNull Class<?> clazz) {", 1)
generator.append(result, "return $ICON_MANAGER_CODE.getIcon(path, clazz);", 2)
generator.append(result, "}", 1)
}
}
override fun appendDeprecatedIconPropertyMapping(generator: IconsClassGenerator, result: StringBuilder, javaDoc: String, oldName: String, iconName: CharSequence, level: Int) {
generator.append(result, "${javaDoc}public static final @Deprecated @NotNull Icon $oldName = $iconName;", level)
}
override fun appendIconProperty(generator: IconsClassGenerator, result: StringBuilder, topLevelClass: String, javaDoc: String, image: ImageInfo, iconName: CharSequence, key: Int, mappings: Map<String, String>?, level: Int) {
val imagePathCodeParameter = image.sourceCodeParameterName
generator.append(
result, "${javaDoc}public static final @NotNull Icon $iconName = " +
"load(${generator.appendExpUIPath(imagePathCodeParameter, mappings)}\"$imagePathCodeParameter\", $key, ${image.getFlags()});", level
)
}
override fun appendDeprecationReplacementClass(generator: IconsClassGenerator, result: StringBuilder, iconName: CharSequence, deprecation: DeprecationData, level: Int) {
generator.append(
result, "public static final @NotNull Icon $iconName = " +
"load(\"${deprecation.replacement}\", ${deprecation.replacementContextClazz}.class);", level
)
}
override fun appendDeprecationReplacementReferenceClass(generator: IconsClassGenerator, result: StringBuilder, iconName: CharSequence, deprecation: DeprecationData, level: Int) {
generator.append(result, "public static final @NotNull Icon $iconName = ${deprecation.replacementReference};", level)
}
}
private object JewelIconClassSpecificsGenerator : IconClassSpecificsGenerator {
override fun amendIconClassName(originalName: String): String = "${originalName}Keys"
override fun pickOutFile(iconClassInfo: IconClassInfo): Path = iconClassInfo.jewelOutFile
override fun pickPackageName(iconClassInfo: IconClassInfo): String? = iconClassInfo.jewelPackageName ?: iconClassInfo.packageName
override fun shouldClassBeFinal(iconClassInfo: IconClassInfo): Boolean = true
override fun appendCustomImports(generator: IconsClassGenerator, result: StringBuilder) {
generator.append(result, "import org.jetbrains.annotations.NotNull;", 0)
result.append('\n')
generator.append(result, "import org.jetbrains.jewel.ui.icon.IntelliJIconKey;", 0)
}
override fun appendTopLevelStatements(generator: IconsClassGenerator, result: StringBuilder, info: IconClassInfo, images: Collection<ImageInfo>) {
// No top level statements needed
}
override fun appendDeprecatedIconPropertyMapping(generator: IconsClassGenerator, result: StringBuilder, javaDoc: String, oldName: String, iconName: CharSequence, level: Int) {
generator.append(result, "${javaDoc}public static final @Deprecated @NotNull IntelliJIconKey $oldName = $iconName;", level)
}
override fun appendIconProperty(generator: IconsClassGenerator, result: StringBuilder, topLevelClass: String, javaDoc: String, image: ImageInfo, iconName: CharSequence, key: Int, mappings: Map<String, String>?, level: Int) {
val imagePathCodeParameter = image.sourceCodeParameterName
val expUiPath = mappings?.let { it[imagePathCodeParameter] } ?: imagePathCodeParameter
generator.append(
result, "${javaDoc}public static final @NotNull IntelliJIconKey $iconName = " +
"new IntelliJIconKey(\"$imagePathCodeParameter\", \"${expUiPath}\", $topLevelClass.class);", level
)
}
override fun appendDeprecationReplacementClass(generator: IconsClassGenerator, result: StringBuilder, iconName: CharSequence, deprecation: DeprecationData, level: Int) {
generator.append(
result, "public static final @NotNull IntelliJIconKey $iconName = " +
"new IntelliJIconKey(\"${deprecation.replacement}\", \"${deprecation.replacement}\", ${deprecation.replacementContextClazz}.class);", level
)
}
override fun appendDeprecationReplacementReferenceClass(generator: IconsClassGenerator, result: StringBuilder, iconName: CharSequence, deprecation: DeprecationData, level: Int) {
generator.append(result, "public static final @NotNull IntelliJIconKey $iconName = ${deprecation.replacementReference};", level)
}
}
} }
private class IconHasher(expectedSize: Int) { private class IconHasher(expectedSize: Int) {
@@ -753,4 +901,4 @@ private class IconHasher(expectedSize: Int) {
check(uniqueGuard.add(hash)) { "uniqueGuard check failed: $fileName | $hash" } check(uniqueGuard.add(hash)) { "uniqueGuard check failed: $fileName | $hash" }
return hash return hash
} }
} }

View File

@@ -152,6 +152,9 @@ class IntellijIconClassGeneratorConfig : IconClasses() {
packageName = "com.android.tools.idea.studiobot.icons", packageName = "com.android.tools.idea.studiobot.icons",
iconDirectory = "icons" iconDirectory = "icons"
) )
"intellij.platform.icons" -> super.getConfigForModule(moduleName)?.copy(generateJewelIcons = true) ?: IntellijIconClassGeneratorModuleConfig(generateJewelIcons = true)
else -> super.getConfigForModule(moduleName) else -> super.getConfigForModule(moduleName)
} }
} }

View File

@@ -29,6 +29,10 @@ data class IntellijIconClassGeneratorModuleConfig(
* The directory where icons are located relative to resource root. * The directory where icons are located relative to resource root.
*/ */
val iconDirectory: String? = null, val iconDirectory: String? = null,
/**
* Enables generation of icon keys for Jewel.
*/
val generateJewelIcons: Boolean = false
) )
abstract class IconClasses { abstract class IconClasses {

File diff suppressed because it is too large Load Diff