From ca66b640af287a3daed026ac6bc9eb52c0ecac13 Mon Sep 17 00:00:00 2001 From: Yuriy Artamonov Date: Tue, 5 Nov 2024 21:40:15 +0100 Subject: [PATCH] [registry] IJPL-162599 Introduce source tracking of Registry values (cherry picked from commit 7d22957cefadaf21805778ca7b30f56bc658fcc1) (cherry picked from commit fbcaaf444416ed69d3f95918d2ea4782d3cabdbf) IJ-MR-154004 GitOrigin-RevId: bb47dda03166638c489791f41f51db47d858bec5 --- ...perimentalFeatureRegistryValueWrapper.java | 5 +- .../openapi/util/registry/RegistryUi.java | 32 +++++++--- .../util/registry/RegistryManagerImpl.kt | 4 +- .../RegistryToAdvancedSettingsMigration.kt | 24 ++++---- .../openapi/util/registry/Registry.kt | 58 ++++++++++++------- .../openapi/util/registry/RegistryValue.kt | 46 +++++++++------ .../util/registry/RegistryValueSource.kt | 22 +++++++ 7 files changed, 127 insertions(+), 64 deletions(-) create mode 100644 platform/util/src/com/intellij/openapi/util/registry/RegistryValueSource.kt diff --git a/platform/lang-impl/src/com/intellij/openapi/util/registry/ExperimentalFeatureRegistryValueWrapper.java b/platform/lang-impl/src/com/intellij/openapi/util/registry/ExperimentalFeatureRegistryValueWrapper.java index 110838cb5293..26f991af27b1 100644 --- a/platform/lang-impl/src/com/intellij/openapi/util/registry/ExperimentalFeatureRegistryValueWrapper.java +++ b/platform/lang-impl/src/com/intellij/openapi/util/registry/ExperimentalFeatureRegistryValueWrapper.java @@ -6,9 +6,6 @@ import com.intellij.openapi.application.Experiments; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; -/** - * @author Konstantin Bulenkov - */ final class ExperimentalFeatureRegistryValueWrapper extends RegistryValue { private final ExperimentalFeature feature; @@ -44,7 +41,7 @@ final class ExperimentalFeatureRegistryValueWrapper extends RegistryValue { } @Override - public void setValue(@NotNull String value) { + public void setValue(@NotNull String value, @NotNull RegistryValueSource source) { boolean enable = Boolean.parseBoolean(value); Experiments.getInstance().setFeatureEnabled(feature.id, enable); } diff --git a/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java b/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java index 06ed92594ecc..89ff486183fa 100644 --- a/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java +++ b/platform/lang-impl/src/com/intellij/openapi/util/registry/RegistryUi.java @@ -75,19 +75,23 @@ public class RegistryUi implements Disposable { myTable.setShowGrid(false); myTable.setVisibleRowCount(15); myTable.setEnableAntialiasing(true); - final MyRenderer r = new MyRenderer(); + MyRenderer r = new MyRenderer(); - final TableColumn c1 = myTable.getColumnModel().getColumn(0); + TableColumn c1 = myTable.getColumnModel().getColumn(0); c1.setPreferredWidth(JBUI.scale(400)); c1.setCellRenderer(r); c1.setHeaderValue("Key"); - final TableColumn c2 = myTable.getColumnModel().getColumn(1); + TableColumn c2 = myTable.getColumnModel().getColumn(1); c2.setPreferredWidth(JBUI.scale(100)); c2.setCellRenderer(r); c2.setHeaderValue("Value"); c2.setCellEditor(new MyEditor()); + TableColumn c3 = myTable.getColumnModel().getColumn(2); + c3.setPreferredWidth(JBUI.scale(100)); + c3.setHeaderValue("Source"); + myDescriptionLabel = new JTextArea(3, 50); myDescriptionLabel.setMargin(JBUI.insets(2)); myDescriptionLabel.setWrapStyleWord(true); @@ -95,8 +99,9 @@ public class RegistryUi implements Disposable { myDescriptionLabel.setEditable(false); myDescriptionLabel.setBackground(UIUtil.getPanelBackground()); myDescriptionLabel.setFont(JBFont.label()); - final JScrollPane label = ScrollPaneFactory.createScrollPane(myDescriptionLabel, SideBorder.NONE); - final JPanel descriptionPanel = new JPanel(new BorderLayout()); + + JScrollPane label = ScrollPaneFactory.createScrollPane(myDescriptionLabel, SideBorder.NONE); + JPanel descriptionPanel = new JPanel(new BorderLayout()); descriptionPanel.add(label, BorderLayout.CENTER); descriptionPanel.setBorder(JBUI.Borders.emptyTop(8)); @@ -155,6 +160,7 @@ public class RegistryUi implements Disposable { keyChanged(rv.getKey()); myModel.fireTableCellUpdated(modelRow, 0); myModel.fireTableCellUpdated(modelRow, 1); + myModel.fireTableCellUpdated(modelRow, 2); } } invalidateActions(); @@ -266,7 +272,7 @@ public class RegistryUi implements Disposable { @Override public int getColumnCount() { - return 2; + return 3; } @Override @@ -275,6 +281,10 @@ public class RegistryUi implements Disposable { return switch (columnIndex) { case 0 -> value.getKey(); case 1 -> value.asString(); + case 2 -> { + RegistryValueSource source = value.getSource(); + yield source != null ? source.name() : ""; + } default -> value; }; } @@ -344,7 +354,6 @@ public class RegistryUi implements Disposable { return "Registry"; } - @Override public JComponent getPreferredFocusedComponent() { return myTable; @@ -377,6 +386,11 @@ public class RegistryUi implements Disposable { processClose(); super.doCancelAction(); } + + @Override + public @NotNull Dimension getInitialSize() { + return new JBDimension(800, 600, false); + } }; return dialog.showAndGet(); @@ -405,7 +419,7 @@ public class RegistryUi implements Disposable { // remove stored value if it is equals to the new value myModifiedValues.remove(key); } - registryValue.setValue(value); + registryValue.setValue(value, RegistryValueSource.USER); } private void restoreDefaults() { @@ -583,7 +597,7 @@ public class RegistryUi implements Disposable { } else if (myValue.isMultiValue()) { String selected = (String)myComboBox.getSelectedItem(); - myValue.setSelectedOption(selected); + myValue.setSelectedOption(selected, RegistryValueSource.USER); } else { setValue(myValue, myField.getText().trim()); diff --git a/platform/platform-impl/src/com/intellij/openapi/util/registry/RegistryManagerImpl.kt b/platform/platform-impl/src/com/intellij/openapi/util/registry/RegistryManagerImpl.kt index b7d5e45c1744..d6e03bc6583d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/util/registry/RegistryManagerImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/util/registry/RegistryManagerImpl.kt @@ -83,7 +83,7 @@ internal class RegistryManagerImpl(coroutineScope: CoroutineScope) : PersistentS log(Registry.loadState(state = state, earlyAccess = EarlyAccessRegistryManager.getOrLoadMap())) } - private fun log(userProperties: Map) { + private fun log(userProperties: Map) { if (userProperties.size <= (if (userProperties.containsKey("ide.firstStartup")) 1 else 0)) { return } @@ -93,7 +93,7 @@ internal class RegistryManagerImpl(coroutineScope: CoroutineScope) : PersistentS val builder = StringBuilder("Registry values changed by user: ") for (key in keys) { if ("ide.firstStartup" != key) { - builder.append(key).append(" = ").append(userProperties[key]).append(", ") + builder.append(key).append(" = ").append(userProperties[key]?.value).append(", ") } } logger().info(builder.substring(0, builder.length - 2)) diff --git a/platform/platform-impl/src/com/intellij/openapi/util/registry/RegistryToAdvancedSettingsMigration.kt b/platform/platform-impl/src/com/intellij/openapi/util/registry/RegistryToAdvancedSettingsMigration.kt index e540ab2dc61d..2011a4eade95 100644 --- a/platform/platform-impl/src/com/intellij/openapi/util/registry/RegistryToAdvancedSettingsMigration.kt +++ b/platform/platform-impl/src/com/intellij/openapi/util/registry/RegistryToAdvancedSettingsMigration.kt @@ -23,14 +23,14 @@ suspend fun migrateRegistryToAdvSettings() { return } - val userProperties = Registry.getInstance().getUserProperties() + val userProperties = Registry.getInstance().getStoredProperties() for (setting in AdvancedSettingBean.EP_NAME.extensionList) { when (setting.id) { "editor.tab.painting" -> migrateEditorTabPainting(userProperties, setting) "vcs.process.ignored" -> migrateVcsIgnoreProcessing(userProperties, setting) "ide.ui.native.file.chooser" -> migrateNativeChooser(userProperties, setting) else -> { - val userProperty = userProperties[setting.id] ?: continue + val userProperty = userProperties[setting.id]?.value ?: continue try { AdvancedSettings.getInstance().setSetting(setting.id, setting.valueFromString(userProperty), setting.type()) userProperties.remove(setting.id) @@ -42,12 +42,12 @@ suspend fun migrateRegistryToAdvSettings() { propertyManager.setValue(propertyName, currentVersion) } -private fun migrateEditorTabPainting(userProperties: MutableMap, setting: AdvancedSettingBean) { - val mode = if (userProperties["editor.old.tab.painting"] == "true") { +private fun migrateEditorTabPainting(userProperties: MutableMap, setting: AdvancedSettingBean) { + val mode = if (userProperties["editor.old.tab.painting"]?.value == "true") { userProperties.remove("editor.old.tab.painting") TabCharacterPaintMode.LONG_ARROW } - else if (userProperties["editor.arrow.tab.painting"] == "true") { + else if (userProperties["editor.arrow.tab.painting"]?.value == "true") { userProperties.remove("editor.arrow.tab.painting") TabCharacterPaintMode.ARROW } @@ -57,14 +57,14 @@ private fun migrateEditorTabPainting(userProperties: MutableMap, AdvancedSettings.getInstance().setSetting(setting.id, mode, setting.type()) } -private fun migrateVcsIgnoreProcessing(userProperties: MutableMap, setting: AdvancedSettingBean) { - if (userProperties["git.process.ignored"] == "false") { +private fun migrateVcsIgnoreProcessing(userProperties: MutableMap, setting: AdvancedSettingBean) { + if (userProperties["git.process.ignored"]?.value == "false") { userProperties.remove("git.process.ignored") } - else if (userProperties["hg4idea.process.ignored"] == "false") { + else if (userProperties["hg4idea.process.ignored"]?.value == "false") { userProperties.remove("hg4idea.process.ignored") } - else if (userProperties["p4.process.ignored"] == "false") { + else if (userProperties["p4.process.ignored"]?.value == "false") { userProperties.remove("p4.process.ignored") } else { @@ -73,10 +73,10 @@ private fun migrateVcsIgnoreProcessing(userProperties: MutableMap, setting: AdvancedSettingBean) { +private fun migrateNativeChooser(userProperties: MutableMap, setting: AdvancedSettingBean) { val enabled = when { - SystemInfo.isWindows -> userProperties.get("ide.win.file.chooser.native") ?: System.getProperty("ide.win.file.chooser.native") - SystemInfo.isMac -> userProperties.get("ide.mac.file.chooser.native") ?: System.getProperty("ide.mac.file.chooser.native") ?: "true" + SystemInfo.isWindows -> userProperties.get("ide.win.file.chooser.native")?.value ?: System.getProperty("ide.win.file.chooser.native") + SystemInfo.isMac -> userProperties.get("ide.mac.file.chooser.native")?.value ?: System.getProperty("ide.mac.file.chooser.native") ?: "true" else -> null } ?: return userProperties.remove("ide.win.file.chooser.native") diff --git a/platform/util/src/com/intellij/openapi/util/registry/Registry.kt b/platform/util/src/com/intellij/openapi/util/registry/Registry.kt index 3a85d319b9a6..5d4efa559f79 100644 --- a/platform/util/src/com/intellij/openapi/util/registry/Registry.kt +++ b/platform/util/src/com/intellij/openapi/util/registry/Registry.kt @@ -1,5 +1,5 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@file:Suppress("ReplaceGetOrSet", "ReplacePutWithAssignment") +@file:Suppress("ReplaceGetOrSet", "ReplacePutWithAssignment", "KDocUnresolvedReference") package com.intellij.openapi.util.registry @@ -19,17 +19,21 @@ import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap import java.util.function.Function -import kotlin.concurrent.Volatile + +@Internal +data class ValueWithSource( + val value: String, + val source: RegistryValueSource, +) /** * Provides a UI to configure internal settings of the IDE. * - * * Plugins can provide their own registry keys using the * `com.intellij.registryKey` extension point (see [com.intellij.openapi.util.registry.RegistryKeyBean] for more details). */ class Registry { - private val userProperties = LinkedHashMap() + private val userProperties = LinkedHashMap() private val values = ConcurrentHashMap() private var contributedKeys = emptyMap() @@ -69,7 +73,7 @@ class Registry { try { valueHandle.asBoolean() } - catch (e: MissingResourceException) { + catch (_: MissingResourceException) { defaultValue } } @@ -127,7 +131,7 @@ class Registry { try { return registry.resolveValue(key).asDouble() } - catch (ignore: MissingResourceException) { + catch (_: MissingResourceException) { return defaultValue } } @@ -187,17 +191,22 @@ class Registry { return intValue(key, defaultValue).coerceIn(minValue, maxValue) } - private fun fromState(state: Element): Map { - val map = LinkedHashMap() + private fun fromState(state: Element): Map { + val map = LinkedHashMap() for (entry in state.getChildren("entry")) { val key = entry.getAttributeValue("key") ?: continue val value = entry.getAttributeValue("value") ?: continue - map.put(key, value) + val source = when (entry.getAttributeValue("source")) { + RegistryValueSource.USER.name -> RegistryValueSource.USER + RegistryValueSource.MANAGER.name -> RegistryValueSource.MANAGER + else -> RegistryValueSource.SYSTEM + } + map.put(key, ValueWithSource(value, source)) } return map } - private fun updateStateInternal(registry: Registry, state: Element?): Map { + private fun updateStateInternal(registry: Registry, state: Element?): Map { val userProperties = registry.userProperties if (state == null) { userProperties.clear() @@ -210,8 +219,8 @@ class Registry { val registryValue = registry.resolveValue(key) val currentValue = registryValue.resolveNotRequiredValue(key) // currentValue == null means value is not in the bundle. Ignore it - if (currentValue != null && currentValue != value) { - registryValue.setValue(value) + if (currentValue != null && currentValue != value.value) { + registryValue.setValue(value.value) } keysToProcess.remove(key) } @@ -225,7 +234,7 @@ class Registry { } @Internal - fun loadState(state: Element?, earlyAccess: Map?): Map { + fun loadState(state: Element?, earlyAccess: Map?): Map { val registry = registry if (registry.isLoaded) { return updateStateInternal(registry, state) @@ -254,7 +263,7 @@ class Registry { try { bundle = loadFromBundledConfig() } - catch (ignored: IOException) { + catch (_: IOException) { } val keys = bundle?.keys ?: emptySet() val result = ArrayList() @@ -275,7 +284,7 @@ class Registry { return result } - private fun isRestartNeeded(map: Map): Boolean { + private fun isRestartNeeded(map: Map): Boolean { val instance = getInstance() for (s in map.keys) { val eachValue = instance.resolveValue(s) @@ -310,14 +319,14 @@ class Registry { registry: Registry, state: Element?, earlyAccess: Map? - ): Map { + ): Map { val userProperties = registry.userProperties userProperties.clear() if (state != null) { val map = fromState(state) for ((key, value) in map) { val registryValue = registry.resolveValue(key) - if (value != registry.getBundleValueOrNull(registryValue.key)) { + if (value.value != registry.getBundleValueOrNull(registryValue.key)) { userProperties.put(key, value) registryValue.resetCache() } @@ -326,7 +335,7 @@ class Registry { if (earlyAccess != null) { // yes, earlyAccess overrides user properties - userProperties.putAll(earlyAccess) + userProperties.putAll(earlyAccess.mapValues { ValueWithSource(it.value, RegistryValueSource.SYSTEM) }) } registry.isLoaded = true @@ -371,7 +380,8 @@ class Registry { if (registryValue.isChangedFromDefault()) { val entryElement = Element("entry") entryElement.setAttribute("key", key) - entryElement.setAttribute("value", value) + entryElement.setAttribute("value", value.value) + entryElement.setAttribute("source", value.source.name) state.addContent(entryElement) } } @@ -379,7 +389,15 @@ class Registry { } @Internal - fun getUserProperties(): MutableMap = userProperties + fun getStoredProperties(): MutableMap = userProperties + + @Deprecated("Use `getStoredProperties`, changes to this map no longer have any effect", ReplaceWith("getStoredProperties()")) + @Internal + fun getUserProperties(): MutableMap { + val copy = mutableMapOf() + copy.putAll(userProperties.mapValues { it.value.value }) + return copy + } @Internal fun restoreDefaults() { diff --git a/platform/util/src/com/intellij/openapi/util/registry/RegistryValue.kt b/platform/util/src/com/intellij/openapi/util/registry/RegistryValue.kt index 4cff98e716fb..f6d0efde91b3 100644 --- a/platform/util/src/com/intellij/openapi/util/registry/RegistryValue.kt +++ b/platform/util/src/com/intellij/openapi/util/registry/RegistryValue.kt @@ -34,6 +34,10 @@ open class RegistryValue @Internal constructor( private var doubleCachedValue = Double.NaN private var booleanCachedValue: Boolean? = null + fun getSource(): RegistryValueSource? { + return registry.getStoredProperties().get(key)?.source + } + open fun asString(): @NlsSafe String { var result = stringCachedValue if (result == null) { @@ -97,17 +101,21 @@ open class RegistryValue @Internal constructor( return null } set(selected) { - val options = asOptions().toMutableList() - for ((i, option) in options.withIndex()) { - val v = option.trimEnd('*') - options.set(i, v) - if (v == selected) { - options.set(i, v.plus("*")) - } - } - setValue("[" + options.joinToString(separator = "|") + "]") + setSelectedOption(selected, RegistryValueSource.SYSTEM) } + fun setSelectedOption(selected: String?, source: RegistryValueSource) { + val options = asOptions().toMutableList() + for ((i, option) in options.withIndex()) { + val v = option.trimEnd('*') + options.set(i, v) + if (v == selected) { + options.set(i, v.plus("*")) + } + } + setValue("[" + options.joinToString(separator = "|") + "]", source) + } + fun isOptionEnabled(option: String): Boolean = selectedOption == option fun asDouble(): Double { @@ -172,8 +180,8 @@ open class RegistryValue @Internal constructor( @Internal fun resolveNotRequiredValue(key: @NonNls String): String? { - registry.getUserProperties().get(key)?.let { - return it + registry.getStoredProperties().get(key)?.let { + return it.value } System.getProperty(key)?.let { @@ -186,8 +194,8 @@ open class RegistryValue @Internal constructor( @Throws(MissingResourceException::class) private fun resolveRequiredValue(key: @NonNls String): String { - registry.getUserProperties().get(key)?.let { - return it + registry.getStoredProperties().get(key)?.let { + return it.value } System.getProperty(key)?.let { @@ -221,14 +229,18 @@ open class RegistryValue @Internal constructor( setValue(value.toString()) } - open fun setValue(value: String) { + fun setValue(value: String) { + setValue(value, RegistryValueSource.SYSTEM) + } + + open fun setValue(value: String, source: RegistryValueSource) { val globalValueChangeListener = registry.valueChangeListener globalValueChangeListener.beforeValueChanged(this) for (each in listeners) { each.beforeValueChanged(this) } resetCache() - registry.getUserProperties().put(key, value) + registry.getStoredProperties().put(key, ValueWithSource(value, source)) LOG.info("Registry value '$key' has changed to '$value'") globalValueChangeListener.afterValueChanged(this) @@ -237,7 +249,7 @@ open class RegistryValue @Internal constructor( } if (!isRestartRequired() && resolveNotRequiredValue(key) == registry.getBundleValueOrNull(key)) { - registry.getUserProperties().remove(key) + registry.getStoredProperties().remove(key) } isChangedSinceAppStart = true @@ -265,7 +277,7 @@ open class RegistryValue @Internal constructor( fun resetToDefault() { val value = registry.getBundleValueOrNull(key) if (value == null) { - registry.getUserProperties().remove(key) + registry.getStoredProperties().remove(key) } else { setValue(value) diff --git a/platform/util/src/com/intellij/openapi/util/registry/RegistryValueSource.kt b/platform/util/src/com/intellij/openapi/util/registry/RegistryValueSource.kt new file mode 100644 index 000000000000..1405c25c51f3 --- /dev/null +++ b/platform/util/src/com/intellij/openapi/util/registry/RegistryValueSource.kt @@ -0,0 +1,22 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.util.registry + +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +enum class RegistryValueSource { + /** + * Values set by user via Registry UI. + */ + USER, + + /** + * Values set programmatically or loaded from configuration. + */ + SYSTEM, + + /** + * Used by cloud or IDE Services provisioning mechanisms. + */ + MANAGER, +} \ No newline at end of file