diff --git a/changelog/CHANGELOG.md b/changelog/CHANGELOG.md index 0d7b2226e..a5e33517f 100644 --- a/changelog/CHANGELOG.md +++ b/changelog/CHANGELOG.md @@ -1,5 +1,10 @@ Changelog --- +# 88.5-1.14.0 [Marketplace Support] + +- Added initial 2024.2 build support +- Should be able to be published to marketplace now. + # 88.5-1.13.0 [2024.1 Build Support] - Added initial 2024.1 build support diff --git a/changelog/RELEASE-NOTES.md b/changelog/RELEASE-NOTES.md index cd237f380..0114ea5ec 100644 --- a/changelog/RELEASE-NOTES.md +++ b/changelog/RELEASE-NOTES.md @@ -1 +1,2 @@ -- Added initial 2024.1 build support +- Added initial 2024.2 build support +- Should be able to be published to marketplace now. diff --git a/gradle.properties b/gradle.properties index 5e4c4116f..5ea9548c6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,13 +2,13 @@ # -> https://www.jetbrains.org/intellij/sdk/docs/reference_guide/intellij_artifacts.html pluginGroup=io.unthrottled -pluginVersion=88.5-1.13.0 -pluginSinceBuild=233.8264.8 -pluginUntilBuild=241.* +pluginVersion=88.5-1.14.0 +pluginSinceBuild=241 +pluginUntilBuild=242.* # Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl # See https://jb.gg/intellij-platform-builds-list for available build versions. -pluginVerifierIdeVersions = 2023.3.2 +pluginVerifierIdeVersions = 2024.1, 2024.2 platformType = IU platformVersion = 2023.3.2 diff --git a/src/main/kotlin/io/unthrottled/doki/TheDokiTheme.kt b/src/main/kotlin/io/unthrottled/doki/TheDokiTheme.kt index 1c14c102d..e214dce6f 100644 --- a/src/main/kotlin/io/unthrottled/doki/TheDokiTheme.kt +++ b/src/main/kotlin/io/unthrottled/doki/TheDokiTheme.kt @@ -7,7 +7,6 @@ import com.intellij.openapi.application.ApplicationActivationListener import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.project.Project -import com.intellij.openapi.startup.StartupManager import com.intellij.openapi.wm.IdeFrame import io.unthrottled.doki.config.ThemeConfig import io.unthrottled.doki.hax.HackComponent.hackLAF @@ -114,7 +113,7 @@ class TheDokiTheme : Disposable { LegacyMigration.newVersionMigration(project) ThemeConfig.instance.version = version ThemeManager.instance.currentTheme.ifPresent { - StartupManager.getInstance(project).runAfterOpened { + ApplicationManager.getApplication().invokeLater { UpdateNotification.display( project, version, @@ -124,7 +123,7 @@ class TheDokiTheme : Disposable { } } - StartupManager.getInstance(project).runAfterOpened { + ApplicationManager.getApplication().invokeLater { PromotionManager.registerPromotion(version) } } diff --git a/src/main/kotlin/io/unthrottled/doki/hax/SvgLoaderHacker.kt b/src/main/kotlin/io/unthrottled/doki/hax/SvgLoaderHacker.kt index 978037e21..06486a6cc 100644 --- a/src/main/kotlin/io/unthrottled/doki/hax/SvgLoaderHacker.kt +++ b/src/main/kotlin/io/unthrottled/doki/hax/SvgLoaderHacker.kt @@ -1,15 +1,17 @@ package io.unthrottled.doki.hax -import com.intellij.ui.svg.setSelectionColorPatcherProvider -import com.intellij.util.SVGLoader import io.unthrottled.doki.icon.ColorPatcher import io.unthrottled.doki.service.PluginService import io.unthrottled.doki.themes.DokiTheme -import java.util.Optional +import io.unthrottled.doki.util.Logging +import io.unthrottled.doki.util.logger +import io.unthrottled.doki.util.runSafely +import io.unthrottled.doki.util.runSafelyWithResult +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Method +import java.lang.reflect.Proxy -typealias PatcherProvider = SVGLoader.SvgElementColorPatcherProvider - -object SvgLoaderHacker { +object SvgLoaderHacker : Logging { /** * Enables the ability to have more than one color patcher. */ @@ -18,30 +20,52 @@ object SvgLoaderHacker { return } - collectOtherPatcher() - .ifPresent { otherPatcher -> - ColorPatcher.setOtherPatcher(otherPatcher) - } - ColorPatcher.setDokiTheme(dokiTheme) - SVGLoader.colorPatcherProvider = ColorPatcher - setSelectionColorPatcherProvider(ColorPatcher) + runSafely({ + setHackedPatcher() + }) { + logger().warn("Unable to set hacked patcher", it) + } } - private fun collectOtherPatcher(): Optional = - Optional.ofNullable( - SVGLoader::class.java.declaredFields - .firstOrNull { it.name == "colorPatcherProvider" }, - ) - .map { ourColorPatcherField -> - ourColorPatcherField.isAccessible = true - ourColorPatcherField.get(null) - } - .filter { it is PatcherProvider } - .filter { it !is ColorPatcher } - .map { - val otherPatcher = it as PatcherProvider - otherPatcher + private fun setHackedPatcher() { + val patcherProxyHandler = + object : InvocationHandler, Logging { + val associatedMethods = ColorPatcher.javaClass.methods.associateBy { it.name } + + override fun invoke( + proxy: Any?, + method: Method?, + arguments: Array?, + ): Any? { + if (method == null) return null + return runSafelyWithResult({ + val methodToInvoke = associatedMethods[method.name] + val usableArguments = arguments ?: emptyArray() + methodToInvoke?.invoke( + ColorPatcher, + *usableArguments, + ) + }) { + logger().warn("unable to invoke proxy handler method", it) + null + } + } } + val patcherProviderClass = Class.forName("com.intellij.util.SVGLoader\$SvgElementColorPatcherProvider") + val proxiedSVGElementColorProvider = + Proxy.newProxyInstance( + patcherProviderClass.classLoader, + arrayOf(patcherProviderClass), + patcherProxyHandler, + ) + val svgLoaderClass = Class.forName("com.intellij.util.SVGLoader") + val setPatcher = svgLoaderClass.declaredMethods.firstOrNull { it.name == "setColorPatcherProvider" } + setPatcher?.invoke(null, proxiedSVGElementColorProvider) + + val clazz = Class.forName("com.intellij.ui.svg.SvgKt") + val setPatcherProvider = clazz.declaredMethods.firstOrNull { it.name == "setSelectionColorPatcherProvider" } + setPatcherProvider?.invoke(null, proxiedSVGElementColorProvider) + } } diff --git a/src/main/kotlin/io/unthrottled/doki/hax/svg/SvgElementColorPatcherProvider.kt b/src/main/kotlin/io/unthrottled/doki/hax/svg/SvgElementColorPatcherProvider.kt new file mode 100644 index 000000000..e765d3b8a --- /dev/null +++ b/src/main/kotlin/io/unthrottled/doki/hax/svg/SvgElementColorPatcherProvider.kt @@ -0,0 +1,15 @@ +package io.unthrottled.doki.hax.svg + +import com.intellij.ui.svg.SvgAttributePatcher + +interface SvgElementColorPatcherProvider { + fun attributeForPath(path: String): SvgAttributePatcher? = null + + /** + * Returns a digest of the current SVG color patcher. + * + * Consider using a two-element array, where the first element is a hash of the input data for the patcher, + * and the second is an ID of the patcher (see [com.intellij.ui.icons.ColorPatcherIdGenerator]). + */ + fun digest(): LongArray +} diff --git a/src/main/kotlin/io/unthrottled/doki/icon/ColorPatcher.kt b/src/main/kotlin/io/unthrottled/doki/icon/ColorPatcher.kt index 022ae717e..9343104ca 100644 --- a/src/main/kotlin/io/unthrottled/doki/icon/ColorPatcher.kt +++ b/src/main/kotlin/io/unthrottled/doki/icon/ColorPatcher.kt @@ -3,15 +3,17 @@ package io.unthrottled.doki.icon import com.google.common.cache.Cache import com.google.common.cache.CacheBuilder import com.intellij.ide.ui.LafManager -import com.intellij.ide.ui.laf.UIThemeLookAndFeelInfoImpl +import com.intellij.ide.ui.UITheme import com.intellij.ui.ColorUtil import com.intellij.ui.JBColor import com.intellij.ui.JBColor.namedColor import com.intellij.ui.svg.SvgAttributePatcher import com.intellij.util.io.DigestUtil -import io.unthrottled.doki.hax.PatcherProvider +import io.unthrottled.doki.hax.svg.SvgElementColorPatcherProvider import io.unthrottled.doki.themes.DokiTheme import io.unthrottled.doki.themes.ThemeManager +import io.unthrottled.doki.util.Logging +import io.unthrottled.doki.util.logger import io.unthrottled.doki.util.runSafely import io.unthrottled.doki.util.runSafelyWithResult import io.unthrottled.doki.util.toHexString @@ -25,7 +27,7 @@ object NoOptPatcher : SvgAttributePatcher { } val noOptPatcherProvider = - object : PatcherProvider { + object : SvgElementColorPatcherProvider { val longArray = longArrayOf(0) override fun digest(): LongArray { @@ -34,9 +36,9 @@ val noOptPatcherProvider = } @Suppress("TooManyFunctions") -object ColorPatcher : PatcherProvider { - private var otherColorPatcherProvider: PatcherProvider = noOptPatcherProvider - private var uiColorPatcherProvider: PatcherProvider = noOptPatcherProvider +object ColorPatcher : SvgElementColorPatcherProvider { + private var otherColorPatcherProvider: SvgElementColorPatcherProvider = noOptPatcherProvider + private var uiColorPatcherProvider: SvgElementColorPatcherProvider = noOptPatcherProvider private lateinit var dokiTheme: DokiTheme private val patcherProviderCache = HashSet() @@ -45,16 +47,50 @@ object ColorPatcher : PatcherProvider { calculateAndSetNewDigest() LafManager.getInstance() ?.currentUIThemeLookAndFeel.toOptional() - .filter { it is UIThemeLookAndFeelInfoImpl } - .map { it as UIThemeLookAndFeelInfoImpl } - .ifPresent { - this.uiColorPatcherProvider = it.theme.colorPatcher ?: noOptPatcherProvider + .map { + it to it.javaClass.methods.firstOrNull { method -> method.name == "getTheme" } } - clearCaches() - } + .filter { it.second != null } + .ifPresent { + val theme = ((it.second?.invoke(it.first)) as UITheme) + val themeClass = UITheme::class.java + val themeClassMethods = themeClass.methods + val attr = themeClassMethods.firstOrNull { method -> method.name == "attributeForPath" } + val digest = themeClassMethods.firstOrNull { method -> method.name == "digest" } + this.uiColorPatcherProvider = + object : SvgElementColorPatcherProvider, Logging { + override fun attributeForPath(path: String): SvgAttributePatcher? { + return runSafelyWithResult({ + val patcherForPath = attr?.invoke(digest, path) + val patchColorsMethod = + patcherForPath?.javaClass + ?.methods?.firstOrNull { method -> method.name == "patchColors" } + object : SvgAttributePatcher { + override fun patchColors(attributes: MutableMap) { + runSafelyWithResult({ + patchColorsMethod + ?.invoke(patcherForPath, attributes) + }) { patchingError -> + logger().warn("unable to patch colors", patchingError) + } + } + } + }) { + logger().warn("Unable to patch path for raisins", it) + null + } + } - fun setOtherPatcher(otherPatcher: PatcherProvider) { - this.otherColorPatcherProvider = otherPatcher + override fun digest(): LongArray { + return runSafelyWithResult({ + digest?.invoke(theme) as LongArray + }) { digestError -> + logger().warn("Unable to get digest", digestError) + longArrayOf() + } + } + } + } clearCaches() } diff --git a/src/main/kotlin/io/unthrottled/doki/integrations/ErrorReporter.kt b/src/main/kotlin/io/unthrottled/doki/integrations/ErrorReporter.kt index cb239d57c..b78726fbf 100644 --- a/src/main/kotlin/io/unthrottled/doki/integrations/ErrorReporter.kt +++ b/src/main/kotlin/io/unthrottled/doki/integrations/ErrorReporter.kt @@ -3,9 +3,9 @@ package io.unthrottled.doki.integrations import com.google.gson.Gson import com.intellij.ide.IdeBundle import com.intellij.ide.ui.LafManager +import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.application.ApplicationNamesInfo import com.intellij.openapi.application.ex.ApplicationInfoEx -import com.intellij.openapi.application.impl.ApplicationInfoImpl import com.intellij.openapi.diagnostic.ErrorReportSubmitter import com.intellij.openapi.diagnostic.IdeaLoggingEvent import com.intellij.openapi.diagnostic.SubmittedReportInfo @@ -120,7 +120,7 @@ class ErrorReporter : ErrorReportSubmitter() { ManagementFactory.getGarbageCollectorMXBeans().stream() .map { it.name }.collect(Collectors.joining(",")) - private fun getBuildInfo(appInfo: ApplicationInfoImpl): String { + private fun getBuildInfo(appInfo: ApplicationInfo): String { var buildInfo = IdeBundle.message("about.box.build.number", appInfo.build.asString()) val cal = appInfo.buildDate var buildDate = "" @@ -132,8 +132,8 @@ class ErrorReporter : ErrorReportSubmitter() { return buildInfo } - private fun getAppName(): Pair { - val appInfo = ApplicationInfoEx.getInstanceEx() as ApplicationInfoImpl + private fun getAppName(): Pair { + val appInfo = ApplicationInfoEx.getInstanceEx() as ApplicationInfo var appName = appInfo.fullApplicationName val edition = ApplicationNamesInfo.getInstance().editionName if (edition != null) appName += " ($edition)" diff --git a/src/main/kotlin/io/unthrottled/doki/integrations/RestClient.kt b/src/main/kotlin/io/unthrottled/doki/integrations/RestClient.kt index 61a1eb78e..4eba22ccf 100644 --- a/src/main/kotlin/io/unthrottled/doki/integrations/RestClient.kt +++ b/src/main/kotlin/io/unthrottled/doki/integrations/RestClient.kt @@ -34,8 +34,8 @@ object RestTools { fun performRequest( url: String, - bodyExtractor: (InputStream) -> T, - ): Optional { + bodyExtractor: (InputStream) -> T & Any, + ): Optional { log.info("Attempting to download remote asset: $url") return runSafelyWithResult({ HttpRequests.request(url) @@ -46,7 +46,7 @@ object RestTools { body.toOptional() }) { log.warn("Unable to get remote asset: $url for raisins", it) - Optional.empty() + Optional.empty() } } }) { diff --git a/src/main/kotlin/io/unthrottled/doki/legacy/EXPUIFixer.kt b/src/main/kotlin/io/unthrottled/doki/legacy/EXPUIFixer.kt index 37e5cc00f..d1838f3d7 100644 --- a/src/main/kotlin/io/unthrottled/doki/legacy/EXPUIFixer.kt +++ b/src/main/kotlin/io/unthrottled/doki/legacy/EXPUIFixer.kt @@ -4,7 +4,7 @@ import com.intellij.ide.ui.LafManager import com.intellij.ide.ui.LafManagerListener import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager -import com.intellij.ui.ExperimentalUI +import com.intellij.ui.NewUI import com.intellij.util.ui.JBUI import io.unthrottled.doki.themes.ThemeManager import javax.swing.UIDefaults @@ -17,9 +17,8 @@ object EXPUIFixer : LafManagerListener, Disposable { connection.subscribe(LafManagerListener.TOPIC, this) } - @Suppress("UnstableApiUsage") fun fixExperimentalUI() { - if (!ExperimentalUI.isNewUI()) return + if (!NewUI.isEnabled()) return overrideSetProperties(0) } diff --git a/src/main/kotlin/io/unthrottled/doki/service/PluginService.kt b/src/main/kotlin/io/unthrottled/doki/service/PluginService.kt index 41db667a7..5033ffde4 100644 --- a/src/main/kotlin/io/unthrottled/doki/service/PluginService.kt +++ b/src/main/kotlin/io/unthrottled/doki/service/PluginService.kt @@ -5,14 +5,16 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.impl.ApplicationInfoImpl +import com.intellij.openapi.application.ex.ApplicationInfoEx import com.intellij.openapi.extensions.PluginId import com.intellij.util.Urls import com.intellij.util.io.HttpRequests import io.unthrottled.doki.util.Logging import io.unthrottled.doki.util.logger import io.unthrottled.doki.util.runSafelyWithResult +import io.unthrottled.doki.util.toOptional import java.util.Collections import java.util.concurrent.Callable @@ -45,8 +47,12 @@ object PluginService : Logging { ) private val PLUGIN_MANAGER_URL by lazy { - ApplicationInfoImpl.getInstanceEx() - .pluginManagerUrl + ApplicationInfo.getInstance() + .toOptional() + .filter { it is ApplicationInfoEx } + .map { it as ApplicationInfoEx } + .map { it.pluginManagerUrl } + .orElseGet { "https://plugins.jetbrains.com" } .trimEnd('/') } private val COMPATIBLE_UPDATE_URL by lazy { "$PLUGIN_MANAGER_URL/api/search/compatibleUpdates" } @@ -81,7 +87,7 @@ object PluginService : Logging { val data = objectMapper.writeValueAsString( CompatibleUpdateRequest( - ApplicationInfoImpl.getInstanceEx() + ApplicationInfo.getInstance() .build.asString(), ids.map { it.idString }, ), diff --git a/src/main/kotlin/io/unthrottled/doki/stickers/MarginService.kt b/src/main/kotlin/io/unthrottled/doki/stickers/MarginService.kt index c7658708d..087b5dea4 100644 --- a/src/main/kotlin/io/unthrottled/doki/stickers/MarginService.kt +++ b/src/main/kotlin/io/unthrottled/doki/stickers/MarginService.kt @@ -16,8 +16,8 @@ class MarginService : Logging { private const val STICKER_Y_OFFSET = 0.05 private const val STICKER_X_OFFSET = 0.03 - val instance: MarginService = - ApplicationManager.getApplication().getService(MarginService::class.java) + val instance: MarginService + get() = ApplicationManager.getApplication().getService(MarginService::class.java) } private val gson = diff --git a/src/main/kotlin/io/unthrottled/doki/themes/impl/ThemeManagerImpl.kt b/src/main/kotlin/io/unthrottled/doki/themes/impl/ThemeManagerImpl.kt index 351a4587f..b23fe1630 100644 --- a/src/main/kotlin/io/unthrottled/doki/themes/impl/ThemeManagerImpl.kt +++ b/src/main/kotlin/io/unthrottled/doki/themes/impl/ThemeManagerImpl.kt @@ -4,7 +4,6 @@ import com.google.gson.Gson import com.intellij.ide.ui.LafManager import com.intellij.ide.ui.laf.UIThemeLookAndFeelInfo import com.intellij.ide.ui.laf.UIThemeLookAndFeelInfoImpl -import com.intellij.util.io.inputStream import io.unthrottled.doki.TheDokiTheme import io.unthrottled.doki.themes.DokiTheme import io.unthrottled.doki.themes.JetBrainsThemeDefinition