IDEA-330211 use kotlinx serialization for ConsentAttributes, use stream parser for UITheme

GitOrigin-RevId: 168f691910c033df5a90fb6b1e0cb47aada129c2
This commit is contained in:
Vladimir Krivosheev
2023-09-03 23:39:20 +00:00
committed by intellij-monorepo-bot
parent c13f33e9ec
commit 75d7c35138
9 changed files with 393 additions and 168 deletions
@@ -32,7 +32,6 @@
<orderEntry type="module" module-name="intellij.platform.ide.core" exported="" />
<orderEntry type="module" module-name="intellij.platform.util.text.matching" />
<orderEntry type="module" module-name="intellij.platform.diagnostic" />
<orderEntry type="library" name="jackson-jr-objects" level="project" />
<orderEntry type="library" name="jackson" level="project" />
</component>
</module>
@@ -1,14 +1,13 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.ui;
import com.fasterxml.jackson.jr.ob.JSON;
import com.fasterxml.jackson.core.JsonFactory;
import com.intellij.AbstractBundle;
import com.intellij.DynamicBundle;
import com.intellij.ide.plugins.cl.PluginAwareClassLoader;
import com.intellij.ide.ui.laf.UIThemeLookAndFeelInfo;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.IconPathPatcher;
import com.intellij.openapi.util.SystemInfoRt;
import com.intellij.ui.ColorHexUtil;
import com.intellij.ui.Gray;
import com.intellij.ui.icons.ImageDataByPathLoader;
@@ -31,6 +30,7 @@ import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.*;
import java.util.function.Function;
@@ -44,11 +44,6 @@ public final class UITheme {
private final UIThemeBean bean;
private static final String OS_MACOS_KEY = "os.mac";
private static final String OS_WINDOWS_KEY = "os.windows";
private static final String OS_LINUX_KEY = "os.linux";
private static final String OS_DEFAULT_KEY = "os.default";
private UITheme(@NotNull UIThemeBean bean) {
this.bean = bean;
}
@@ -73,18 +68,9 @@ public final class UITheme {
return bean.author;
}
// it caches classes - must be not extracted to util class
// .disable(JSON.Feature.PRESERVE_FIELD_ORDERING) - cannot be disabled, for unknown reason order is important,
// for example, button label font color for light theme is not white, but black
private static final JSON JSON_READER = JSON.builder()
.enable(JSON.Feature.READ_ONLY)
.disable(JSON.Feature.USE_DEFERRED_MAPS)
.disable(JSON.Feature.HANDLE_JAVA_BEANS)
.build();
@ApiStatus.Internal
public static @NotNull UITheme loadFromJson(@NotNull InputStream stream, @NotNull @NonNls String themeId) throws IOException {
UIThemeBean theme = JSON_READER.beanFrom(UIThemeBean.class, stream);
UIThemeBean theme = UIThemeBean.Companion.readTheme(new JsonFactory().createParser(stream));
theme.id = themeId;
return postProcessTheme(theme, findParentTheme(theme), null, Function.identity());
}
@@ -93,7 +79,7 @@ public final class UITheme {
@NotNull @NonNls String themeId,
@Nullable ClassLoader provider,
@NotNull Function<? super String, String> iconsMapper) throws IOException {
UIThemeBean theme = JSON_READER.beanFrom(UIThemeBean.class, data);
UIThemeBean theme = UIThemeBean.Companion.readTheme(new JsonFactory().createParser(data));
theme.id = themeId;
return postProcessTheme(theme, findParentTheme(theme), provider, iconsMapper);
}
@@ -105,7 +91,7 @@ public final class UITheme {
@NotNull Function<? super String, String> iconsMapper,
@Nullable UITheme defaultDarkParent,
@Nullable UITheme defaultLightParent) throws IOException {
UIThemeBean theme = JSON_READER.beanFrom(UIThemeBean.class, data);
UIThemeBean theme = UIThemeBean.Companion.readTheme(new JsonFactory().createParser(data));
theme.id = themeId;
if (theme.dark && parentTheme == null) {
return postProcessTheme(theme, defaultDarkParent, provider, iconsMapper);
@@ -120,11 +106,9 @@ public final class UITheme {
@Nullable UITheme parentTheme,
@Nullable ClassLoader provider,
@NotNull Function<? super String, String> iconsMapper) throws IllegalStateException {
normalizeKeyPaths(theme);
if (parentTheme != null) {
UIThemeBean.Companion.importFromParentTheme(theme, parentTheme.bean);
}
UIThemeBean.Companion.putDefaultsIfAbsent(theme);
return new UITheme(loadFromJson(theme, provider, iconsMapper));
}
@@ -143,60 +127,6 @@ public final class UITheme {
return null;
}
/**
* Flatten
* <pre>{@code "Editor": { "SearchField" : { "borderInsets" : "7,10,7,8" } }}</pre>
* to
* <pre>{@code "Editor.SearchField.borderInsets" : "7,10,7,8"}</pre>
* in internal representation.
* <p>
* We also resolve per-OS keys here:
* <pre> {@code
* "Menu.borderColor": {
* "os.default": "Grey12",
* "os.windows": "Blue12"
* }
* }</pre>
* <p>
* This is helpful when we need to check if some key was already set in {@link UIThemeBean.Companion#putDefaultsIfAbsent},
* and to make overriding parentTheme keys independent of used form.
* <p>
* NB: we intentionally do not expand "*" patterns here.
*/
private static void normalizeKeyPaths(@NotNull UIThemeBean theme) {
if (theme.ui == null) {
return;
}
Map<String, Object> result = new LinkedHashMap<>();
for (Map.Entry<String, ?> entry : theme.ui.entrySet()) {
normalizeKeyValue(entry.getKey(), entry.getValue(), result);
}
theme.ui = result;
}
private static void normalizeKeyValue(@NotNull String keyPrefix,
@NotNull Object value,
@NotNull Map<String, Object> result) {
if (value instanceof Map) {
@SuppressWarnings("unchecked") Map<String, Object> valueMap = (Map<String, Object>)value;
Object osValue = getOSCustomization(valueMap);
if (osValue == null) {
for (Map.Entry<String, Object> entry : valueMap.entrySet()) {
String uiKey = createUIKey(keyPrefix, entry.getKey());
normalizeKeyValue(uiKey, entry.getValue(), result);
}
}
else {
normalizeKeyValue(keyPrefix, osValue, result);
}
}
else {
result.put(keyPrefix, value);
}
}
private static @NotNull UIThemeBean loadFromJson(@NotNull UIThemeBean theme,
@Nullable ClassLoader provider,
@NotNull Function<? super String, String> iconsMapper) throws IllegalStateException {
@@ -422,7 +352,7 @@ public final class UITheme {
return bean.editorScheme;
}
public String @Nullable [] getAdditionalEditorSchemes() {
public @Nullable List<String> getAdditionalEditorSchemes() {
return bean.additionalEditorSchemes;
}
@@ -530,18 +460,6 @@ public final class UITheme {
}
}
private static @Nullable Object getOSCustomization(@NotNull Map<String, Object> map) {
String osKey = SystemInfoRt.isWindows ? OS_WINDOWS_KEY :
SystemInfoRt.isMac ? OS_MACOS_KEY :
SystemInfoRt.isLinux ? OS_LINUX_KEY : null;
if (osKey != null && map.containsKey(osKey)) {
return map.get(osKey);
}
else {
return map.get(OS_DEFAULT_KEY);
}
}
@SuppressWarnings("unchecked")
private static void addPattern(String key, Object value, UIDefaults defaults) {
Object o = defaults.get("*");
@@ -1,35 +1,20 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("ReplacePutWithAssignment")
@file:Suppress("ReplacePutWithAssignment", "ReplaceGetOrSet")
package com.intellij.ide.ui
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonToken
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.IconPathPatcher
import com.intellij.ui.ExperimentalUI.Companion.isNewUI
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.ui.ExperimentalUI
import com.intellij.util.SVGLoader.SvgElementColorPatcherProvider
import java.util.*
import java.util.function.BiFunction
internal class UIThemeBean {
companion object {
/**
* Ensure that the old themes are not missing some vital keys.
*
* We are patching them here instead of using [com.intellij.ui.JBColor.namedColor] fallback
* to make sure [javax.swing.UIManager.getColor] works properly.
*/
fun putDefaultsIfAbsent(theme: UIThemeBean) {
var ui = theme.ui
if (ui == null) {
ui = LinkedHashMap()
theme.ui = ui
}
if (isNewUI()) {
ui.putIfAbsent("EditorTabs.underlineArc", "4")
// require theme to specify ToolWindow stripe button colors explicitly, without "*"
ui.putIfAbsent("ToolWindow.Button.selectedBackground", "#3573F0")
ui.putIfAbsent("ToolWindow.Button.selectedForeground", "#FFFFFF")
}
}
fun importFromParentTheme(theme: UIThemeBean, parentTheme: UIThemeBean) {
theme.ui = importMapFromParentTheme(theme.ui, parentTheme.ui)
theme.icons = importMapFromParentTheme(theme.icons, parentTheme.icons)
@@ -39,7 +24,8 @@ internal class UIThemeBean {
theme.iconColorsOnSelection = importMapFromParentTheme(theme.iconColorsOnSelection, parentTheme.iconColorsOnSelection)
}
private fun importMapFromParentTheme(themeMap: MutableMap<String, Any?>?, parentThemeMap: Map<String, Any?>?): MutableMap<String, Any?>? {
private fun importMapFromParentTheme(themeMap: MutableMap<String, Any?>?,
parentThemeMap: Map<String, Any?>?): MutableMap<String, Any?>? {
if (parentThemeMap == null) {
return themeMap
}
@@ -53,56 +39,338 @@ internal class UIThemeBean {
}
return result
}
fun readTheme(parser: JsonParser): UIThemeBean {
check(parser.nextToken() == JsonToken.START_OBJECT)
val bean = UIThemeBean()
while (true) {
when (parser.nextToken()) {
JsonToken.START_OBJECT -> {
val fieldName = parser.currentName()
// ordered map is required (not clear why)
val map = LinkedHashMap<String, Any?>()
readFlatMapFromJson(parser, map)
when (fieldName) {
"icons" -> bean.icons = map
"background" -> bean.background = map
"emptyFrameBackground" -> bean.emptyFrameBackground = map
"colors" -> bean.colors = map
"iconColorsOnSelection" -> bean.iconColorsOnSelection = map
"ui" -> {
putDefaultsIfAbsent(map)
bean.ui = map
}
"UIDesigner" -> {
parser.skipChildren()
}
else -> {
logger<UIThemeBean>().warn("Unknown field: $fieldName")
}
}
}
JsonToken.END_OBJECT -> {
}
JsonToken.START_ARRAY -> {
val fieldName = parser.currentName()
val list = ArrayList<String>()
while (parser.nextToken() != JsonToken.END_ARRAY) {
when (parser.currentToken()) {
JsonToken.VALUE_STRING -> {
list.add(parser.valueAsString)
}
else -> {}
}
}
when (fieldName) {
"additionalEditorSchemes" -> {
bean.additionalEditorSchemes = list
}
else -> {
logger<UIThemeBean>().warn("Unknown field: ${parser.currentName()}")
}
}
}
JsonToken.VALUE_STRING -> {
when (parser.currentName()) {
"id" -> bean.id = parser.valueAsString
"name" -> bean.name = parser.valueAsString
"nameKey" -> bean.nameKey = parser.valueAsString
"parentTheme" -> bean.parentTheme = parser.valueAsString
"resourceBundle" -> bean.resourceBundle = parser.valueAsString
"author" -> bean.author = parser.valueAsString
"editorScheme" -> bean.editorScheme = parser.valueAsString
"editorSchemeName" -> bean.editorSchemeName = parser.valueAsString
}
}
JsonToken.VALUE_TRUE -> readTopLevelBoolean(parser, bean, value = true)
JsonToken.VALUE_FALSE -> readTopLevelBoolean(parser, bean, value = false)
JsonToken.FIELD_NAME -> {
}
null -> break
else -> {
logger<UIThemeBean>().warn("Unknown field: ${parser.currentName()}")
}
}
}
putDefaultsIfAbsent(bean)
return bean
}
private fun readTopLevelBoolean(parser: JsonParser, bean: UIThemeBean, value: Boolean) {
when (parser.currentName()) {
"dark" -> bean.dark = value
}
}
}
@Transient
@JvmField
var providerClassLoader: ClassLoader? = null
@JvmField
var name: String? = null
@JvmField
var dark = false
@JvmField
var author: String? = null
@JvmField
var id: String? = null
@JvmField
var editorScheme: String? = null
var name: String? = null
@JvmField
var nameKey: String? = null
@JvmField
var parentTheme: String? = null
@JvmField
var additionalEditorSchemes: Array<String>? = null
var resourceBundle: String? = "messages.IdeBundle"
@JvmField
var author: String? = null
@JvmField
var editorScheme: String? = null
@JvmField
var editorSchemeName: String? = null
@JvmField
var dark: Boolean = false
@JvmField
var additionalEditorSchemes: List<String>? = null
@JvmField
var ui: MutableMap<String, Any?>? = null
@JvmField
var icons: MutableMap<String, Any?>? = null
@JvmField
var background: MutableMap<String, Any?>? = null
@JvmField
var emptyFrameBackground: MutableMap<String, Any?>? = null
@JvmField
var colors: MutableMap<String, Any?>? = null
@JvmField
var iconColorsOnSelection: MutableMap<String, Any?>? = null
@JvmField
@Transient
var patcher: IconPathPatcher? = null
@JvmField
var background: MutableMap<String, Any?>? = null
@JvmField
var emptyFrameBackground: MutableMap<String, Any?>? = null
@JvmField
var colors: MutableMap<String, Any?>? = null
@JvmField
var iconColorsOnSelection: MutableMap<String, Any?>? = null
@JvmField
var editorSchemeName: String? = null
@JvmField
@Transient
var colorPatcher: SvgElementColorPatcherProvider? = null
@JvmField
@Transient
var selectionColorPatcher: SvgElementColorPatcherProvider? = null
}
@JvmField
var resourceBundle: String? = "messages.IdeBundle"
@JvmField
var nameKey: String? = null
}
/**
* Flatten example: `"Editor": { "SearchField": { "borderInsets": "7,10,7,8" } }` is flattened to
* `"Editor.SearchField.borderInsets": "7,10,7,8` in internal representation.
*
* Per-OS keys are also resolved as shown below:
* ```json
* "Menu.borderColor": {
* "os.default": "Grey12",
* "os.windows": "Blue12"
* }
* ```
*
* This is useful when we need to validate if a certain key was already set in [putDefaultsIfAbsent],
* and to uniformly override parentTheme keys regardless of used format.
*
* Note: we intentionally do not expand "*" patterns here.
*/
private fun readFlatMapFromJson(parser: JsonParser, result: MutableMap<String, Any?>) {
check(parser.currentToken() == JsonToken.START_OBJECT)
val prefix = ArrayDeque<String>()
val path = StringBuilder()
var currentFieldName: String? = null
var level = 1
while (true) {
when (parser.nextToken()) {
JsonToken.START_OBJECT -> {
level++
prefix.addLast(currentFieldName!!)
currentFieldName = null
}
JsonToken.END_OBJECT -> {
level--
prefix.pollLast()
currentFieldName = null
if (level == 0) {
assert(prefix.isEmpty())
break
}
}
JsonToken.START_ARRAY -> {
val fieldName = parser.currentName()
while (true) {
when (parser.nextToken()) {
JsonToken.END_ARRAY -> break
JsonToken.VALUE_STRING -> {
if (!prefix.isEmpty()) {
prefix.joinTo(buffer = path, separator = "/")
path.append('.')
}
path.append(fieldName)
result.put(path.toString(), parser.text)
path.setLength(0)
}
else -> {
logError(parser)
}
}
}
}
JsonToken.VALUE_STRING -> {
putEntry(prefix, result, parser, path) { parser.text }
}
JsonToken.VALUE_NUMBER_INT -> {
putEntry(prefix, result, parser, path) { parser.intValue }
}
JsonToken.VALUE_NUMBER_FLOAT -> {
putEntry(prefix, result, parser, path) { parser.doubleValue }
}
JsonToken.VALUE_FALSE -> {
putEntry(prefix, result, parser, path) { false }
}
JsonToken.VALUE_TRUE -> {
putEntry(prefix, result, parser, path) { true }
}
JsonToken.VALUE_NULL -> {
}
JsonToken.FIELD_NAME -> {
currentFieldName = parser.currentName()
}
null -> {
break
}
else -> {
logError(parser)
}
}
}
result.replaceAll(BiFunction { _, u -> if (u is OsDefaultValue) u.v else u })
}
private const val OS_MACOS_KEY = "os.mac"
private const val OS_WINDOWS_KEY = "os.windows"
private const val OS_LINUX_KEY = "os.linux"
private const val OS_DEFAULT_KEY = "os.default"
private val osKey = when {
SystemInfoRt.isWindows -> OS_WINDOWS_KEY
SystemInfoRt.isMac -> OS_MACOS_KEY
else -> OS_LINUX_KEY
}
private fun putEntry(prefix: Deque<String>,
result: MutableMap<String, Any?>,
parser: JsonParser,
path: StringBuilder,
getter: () -> Any?) {
if (!prefix.isEmpty()) {
prefix.joinTo(buffer = path, separator = ".")
}
val key = parser.currentName()
val value = getter()
when (key) {
osKey -> {
}
OS_WINDOWS_KEY, OS_MACOS_KEY, OS_LINUX_KEY -> {
path.setLength(0)
return
}
OS_DEFAULT_KEY -> {
val compositeKey = path.toString()
path.setLength(0)
val oldValue = result.putIfAbsent(compositeKey, OsDefaultValue(value))
if (oldValue is OsDefaultValue) {
logger<UIThemeBean>().error("Duplicated value: (value=$value, compositeKey=$compositeKey)")
}
return
}
"UI" -> {
path.append(key)
}
else -> {
if (path.isNotEmpty()) {
path.append('.')
}
path.append(key)
}
}
result.put(path.toString(), value)
path.setLength(0)
}
private class OsDefaultValue(@JvmField val v: Any?)
private fun logError(parser: JsonParser) {
logger<UIThemeBean>().warn("JSON contains data in unsupported format (token=${parser.currentToken}): ${parser.currentValue()}")
}
/**
* Ensure that the old themes are not missing some vital keys.
*
* We are patching them here instead of using [com.intellij.ui.JBColor.namedColor] fallback
* to make sure [javax.swing.UIManager.getColor] works properly.
*/
private fun putDefaultsIfAbsent(theme: UIThemeBean) {
if (!ExperimentalUI.isNewUI()) {
return
}
var ui = theme.ui
if (ui == null) {
ui = LinkedHashMap()
theme.ui = ui
putDefaultsIfAbsent(ui)
}
}
private fun putDefaultsIfAbsent(ui: MutableMap<String, Any?>) {
if (!ExperimentalUI.isNewUI()) {
return
}
ui.putIfAbsent("EditorTabs.underlineArc", "4")
// require theme to specify ToolWindow stripe button colors explicitly, without "*"
ui.putIfAbsent("ToolWindow.Button.selectedBackground", "#3573F0")
ui.putIfAbsent("ToolWindow.Button.selectedForeground", "#FFFFFF")
}
@@ -119,7 +119,6 @@
<orderEntry type="module" module-name="intellij.platform.statistics.uploader" />
<orderEntry type="library" name="zip-signer" level="project" />
<orderEntry type="module" module-name="intellij.platform.util.zip" />
<orderEntry type="library" name="jackson-jr-objects" level="project" />
<orderEntry type="module" module-name="intellij.platform.remote.core" exported="" />
<orderEntry type="library" name="icu4j" level="project" />
<orderEntry type="module" module-name="intellij.platform.util.xmlDom" />
@@ -233,9 +233,11 @@ private suspend fun preInitApp(app: ApplicationImpl,
euaTaskDeferred?.await()?.invoke()
val installIconPatcherJob = if (loadIconMapping == null) null else launch {
loadIconMapping.join()
ExperimentalUI.getInstance().installIconPatcher()
if (loadIconMapping != null) {
launch {
loadIconMapping.join()
ExperimentalUI.getInstance().installIconPatcher()
}
}
coroutineScope {
@@ -1,15 +1,55 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.gdpr;
package com.intellij.ide.gdpr
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
/**
* Data structure describing all possible Consent JSON attributes
*/
final class ConsentAttributes {
public String consentId;
public String version;
public String text;
public String printableName;
public boolean accepted;
public boolean deleted;
public long acceptanceTime;
@Serializable
internal class ConsentAttributes {
companion object {
@OptIn(ExperimentalSerializationApi::class)
private val jsonConfig by lazy {
Json {
ignoreUnknownKeys = true
isLenient = true
encodeDefaults = false
prettyPrint = true
prettyPrintIndent = " "
}
}
fun readListFromJson(json: String): List<ConsentAttributes> {
return jsonConfig.decodeFromString<List<ConsentAttributes>>(json)
}
fun writeListToJson(list: List<ConsentAttributes>): String {
return jsonConfig.encodeToString<List<ConsentAttributes>>(list)
}
}
@JvmField
var consentId: String? = null
@JvmField
var version: String? = null
@JvmField
var text: String? = null
@JvmField
var printableName: String? = null
@JvmField
var accepted: Boolean = false
@JvmField
var deleted: Boolean = false
@JvmField
var acceptanceTime: Long = 0
}
@@ -1,9 +1,6 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.gdpr;
import com.fasterxml.jackson.jr.ob.JSON;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.intellij.diagnostic.LoadingState;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
@@ -424,14 +421,16 @@ public final class ConsentOptions {
}
private @NotNull Collection<ConsentAttributes> fromJson(@Nullable String json) {
if (json == null || json.isEmpty()) {
return Collections.emptyList();
}
try {
List<ConsentAttributes> data = json == null || json.isEmpty() ? null : JSON.std.listOfFrom(ConsentAttributes.class, json);
if (data != null) {
for (ConsentAttributes attributes : data) {
attributes.consentId = lookupConsentID(attributes.consentId);
}
return data;
List<ConsentAttributes> data = ConsentAttributes.Companion.readListFromJson(json);
for (ConsentAttributes attributes : data) {
attributes.consentId = lookupConsentID(attributes.consentId);
}
return data;
}
catch (Throwable e) {
LOG.info(e);
@@ -440,15 +439,14 @@ public final class ConsentOptions {
}
private @NotNull String consentsToJson(@NotNull Stream<Consent> consents) {
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
return gson.toJson(consents.map(consent -> {
return ConsentAttributes.Companion.writeListToJson(consents.map(consent -> {
final ConsentAttributes attribs = consent.toConsentAttributes();
final String prefix = getProductConsentKind(myProductCode, attribs.consentId);
if (prefix != null) {
attribs.consentId = prefix;
}
return attribs;
}).toArray());
}).toList());
}
private static @NotNull String confirmedConsentToExternalString(@NotNull Stream<ConfirmedConsent> consents) {
@@ -138,11 +138,12 @@ private fun readDataFromJson(parser: JsonParser, result: MutableMap<String, Stri
}
private fun addWithCheck(result: MutableMap<String, String>, parser: JsonParser, path: StringBuilder) {
val oldValue = result.get(parser.text)
if (oldValue != null && oldValue != path.toString()) {
logger<IconMapLoader>().error("Double icon mapping: ${parser.text} -> $oldValue or $path")
val key = parser.text
val p = path.toString()
val oldValue = result.put(key, p)
if (oldValue != null && oldValue != p) {
logger<IconMapLoader>().error("Double icon mapping: $key -> $oldValue or $path")
}
result.put(parser.text, path.toString())
}
private fun logError(parser: JsonParser) {
@@ -319,7 +319,7 @@ public final class EditorColorsManagerImpl extends EditorColorsManager implement
for (UIThemeLookAndFeelInfo laf : UiThemeProviderListManager.Companion.getInstance().getLaFs()) {
UITheme theme = laf.getTheme();
String[] schemes = theme.getAdditionalEditorSchemes();
List<String> schemes = theme.getAdditionalEditorSchemes();
PluginDescriptor pluginDescriptor = getPluginDescriptor(theme);
if (schemes != null) {
for (String scheme : schemes) {