[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
This commit is contained in:
Yuriy Artamonov
2024-11-05 21:40:15 +01:00
committed by intellij-monorepo-bot
parent 7ed4c31470
commit ca66b640af
7 changed files with 127 additions and 64 deletions

View File

@@ -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);
}

View File

@@ -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());

View File

@@ -83,7 +83,7 @@ internal class RegistryManagerImpl(coroutineScope: CoroutineScope) : PersistentS
log(Registry.loadState(state = state, earlyAccess = EarlyAccessRegistryManager.getOrLoadMap()))
}
private fun log(userProperties: Map<String, String>) {
private fun log(userProperties: Map<String, ValueWithSource>) {
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<RegistryManager>().info(builder.substring(0, builder.length - 2))

View File

@@ -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<String, String>, setting: AdvancedSettingBean) {
val mode = if (userProperties["editor.old.tab.painting"] == "true") {
private fun migrateEditorTabPainting(userProperties: MutableMap<String, ValueWithSource>, 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<String, String>,
AdvancedSettings.getInstance().setSetting(setting.id, mode, setting.type())
}
private fun migrateVcsIgnoreProcessing(userProperties: MutableMap<String, String>, setting: AdvancedSettingBean) {
if (userProperties["git.process.ignored"] == "false") {
private fun migrateVcsIgnoreProcessing(userProperties: MutableMap<String, ValueWithSource>, 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<String, String
AdvancedSettings.getInstance().setSetting(setting.id, false, setting.type())
}
private fun migrateNativeChooser(userProperties: MutableMap<String, String>, setting: AdvancedSettingBean) {
private fun migrateNativeChooser(userProperties: MutableMap<String, ValueWithSource>, 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")

View File

@@ -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<String, String>()
private val userProperties = LinkedHashMap<String, ValueWithSource>()
private val values = ConcurrentHashMap<String, RegistryValue>()
private var contributedKeys = emptyMap<String, RegistryKeyDescriptor>()
@@ -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<String, String> {
val map = LinkedHashMap<String, String>()
private fun fromState(state: Element): Map<String, ValueWithSource> {
val map = LinkedHashMap<String, ValueWithSource>()
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<String, String> {
private fun updateStateInternal(registry: Registry, state: Element?): Map<String, ValueWithSource> {
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<String, String>?): Map<String, String> {
fun loadState(state: Element?, earlyAccess: Map<String, String>?): Map<String, ValueWithSource> {
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<RegistryValue>()
@@ -275,7 +284,7 @@ class Registry {
return result
}
private fun isRestartNeeded(map: Map<String, String>): Boolean {
private fun isRestartNeeded(map: Map<String, ValueWithSource>): 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<String, String>?
): Map<String, String> {
): Map<String, ValueWithSource> {
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<String, String> = userProperties
fun getStoredProperties(): MutableMap<String, ValueWithSource> = userProperties
@Deprecated("Use `getStoredProperties`, changes to this map no longer have any effect", ReplaceWith("getStoredProperties()"))
@Internal
fun getUserProperties(): MutableMap<String, String> {
val copy = mutableMapOf<String, String>()
copy.putAll(userProperties.mapValues { it.value.value })
return copy
}
@Internal
fun restoreDefaults() {

View File

@@ -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)

View File

@@ -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,
}