diff --git a/platform/platform-impl/src/com/intellij/openapi/options/advanced/AdvancedSettingsImpl.kt b/platform/platform-impl/src/com/intellij/openapi/options/advanced/AdvancedSettingsImpl.kt index 2684a0e04f21..13a8a9426960 100644 --- a/platform/platform-impl/src/com/intellij/openapi/options/advanced/AdvancedSettingsImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/options/advanced/AdvancedSettingsImpl.kt @@ -21,6 +21,7 @@ import org.jetbrains.annotations.Nls import org.jetbrains.annotations.TestOnly import java.util.* import java.util.concurrent.ConcurrentHashMap +import kotlin.jvm.Throws class AdvancedSettingBean : PluginAware, KeyedLazyInstance { companion object { @@ -211,6 +212,7 @@ class AdvancedSettingsImpl : AdvancedSettings(), PersistentStateComponentWithMod private val epCollector = KeyedExtensionCollector(AdvancedSettingBean.EP_NAME.name) private val internalState = ConcurrentHashMap() + private val unknownValues = ConcurrentHashMap() private val defaultValueCache = ConcurrentHashMap() private var modificationCount = 0L @@ -218,11 +220,15 @@ class AdvancedSettingsImpl : AdvancedSettings(), PersistentStateComponentWithMod AdvancedSettingBean.EP_NAME.addExtensionPointListener(object : ExtensionPointListener { override fun extensionAdded(extension: AdvancedSettingBean, pluginDescriptor: PluginDescriptor) { logger.info("Extension added ${pluginDescriptor.pluginId}: ${extension.id}") + val rawValue = unknownValues.remove(extension.id) ?: return + internalState[extension.id] = extension.valueFromString(rawValue) } override fun extensionRemoved(extension: AdvancedSettingBean, pluginDescriptor: PluginDescriptor) { logger.info("Extension removed ${pluginDescriptor.pluginId}: ${extension.id}") defaultValueCache.remove(extension.id) + val currentValue = internalState.remove(extension.id) ?: return + unknownValues[extension.id] = extension.valueToString(currentValue) } }, this) } @@ -233,7 +239,9 @@ class AdvancedSettingsImpl : AdvancedSettings(), PersistentStateComponentWithMod if (logger.isDebugEnabled) { logger.debug("Getting advanced settings state: $internalState") } - return AdvancedSettingsState().also { it.settings = internalState.map { (k, v) -> k to getOption(k).valueToString(v) }.toMap() } + val retval = AdvancedSettingsState() + retval.settings = HashMap(unknownValues).apply { putAll(internalState.map { (k, v) -> k to getOption(k).valueToString(v) }.toMap()) } + return retval } override fun loadState(state: AdvancedSettingsState) { @@ -241,7 +249,15 @@ class AdvancedSettingsImpl : AdvancedSettings(), PersistentStateComponentWithMod logger.debug("Will load advanced settings state: ${state.settings}. Current state: ${this.internalState}") } this.internalState.clear() - state.settings.mapNotNull { (k, v) -> getOptionOrNull(k)?.let { option -> k to option.valueFromString(v) } }.toMap(this.internalState) + state.settings.forEach { (k, v) -> + val optionOrNull = getOptionOrNull(k) + if (optionOrNull != null) { + this.internalState.put(k, optionOrNull.valueFromString(v)) + } + else { + unknownValues.put(k, v) + } + } } override fun getStateModificationCount(): Long = modificationCount @@ -301,10 +317,7 @@ class AdvancedSettingsImpl : AdvancedSettings(), PersistentStateComponentWithMod private fun getOptionOrNull(id: String): AdvancedSettingBean? { val bean = epCollector.findSingle(id) if (bean == null) { - if (ApplicationManager.getApplication().isEAP) - logger.error("Cannot find advanced setting $id", Throwable()) - else - logger.warn("Cannot find advanced setting $id", Throwable()) + logger.info("Cannot find advanced setting $id") } return bean } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/options/advanced/AdvancedSettingsTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/options/advanced/AdvancedSettingsTest.kt new file mode 100644 index 000000000000..43711237fde5 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/options/advanced/AdvancedSettingsTest.kt @@ -0,0 +1,43 @@ +// 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.options.advanced + +import com.intellij.idea.TestFor +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.LightPlatformTestCase + +class AdvancedSettingsTest : LightPlatformTestCase() { + + + @TestFor(issues = ["IJPL-172170"]) + fun testUnknownSettingPresent() { + + val advancedSettings = AdvancedSettings.getInstance() as AdvancedSettingsImpl + val state = AdvancedSettingsImpl.AdvancedSettingsState() + state.settings = mutableMapOf("bean.id" to "bean.value") + advancedSettings.loadState(state) + assertTrue(advancedSettings.state.settings.containsKey("bean.id")) + } + + @TestFor(issues = ["IJPL-172170"]) + fun testUnknownLoadedAfterEPEnabled() { + val advancedSettingsService = AdvancedSettings.getInstance() as AdvancedSettingsImpl + val state = AdvancedSettingsImpl.AdvancedSettingsState() + state.settings = mutableMapOf("bean.id" to "bean.value") + advancedSettingsService.loadState(state) + + val meinBean = AdvancedSettingBean().apply { + id = "bean.id" + groupKey = "mein.group" + defaultValue = "MEGA-DEFAULT" + } + val disposable = Disposable { + assertTrue(advancedSettingsService.state.settings.containsKey("bean.id")) + } + AdvancedSettingBean.EP_NAME.point.registerExtension(meinBean, disposable) + assertTrue(AdvancedSettings.getString("bean.id") == "bean.value") + assertTrue(advancedSettingsService.state.settings.containsKey("bean.id")) + Disposer.dispose(disposable) + } + +} \ No newline at end of file