mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
Cleanup (minor optimization; typos; formatting)
GitOrigin-RevId: 8e141475419e5987111a7e039cb318230c994697
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3160e0cf4e
commit
60f1bdbd8b
@@ -69,7 +69,7 @@ open class ApplicationStoreImpl(private val app: Application) : ComponentStoreWi
|
||||
|
||||
coroutineScope {
|
||||
launch {
|
||||
super.doSave(saveResult = saveResult, forceSavingAllSettings = forceSavingAllSettings)
|
||||
super.doSave(saveResult, forceSavingAllSettings)
|
||||
}
|
||||
|
||||
val projectManager = serviceAsync<ProjectManager>() as ProjectManagerEx
|
||||
@@ -110,7 +110,7 @@ class ApplicationStateStorageManager(pathMacroManager: PathMacroManager? = null,
|
||||
Files.deleteIfExists(storage.file)
|
||||
}
|
||||
else {
|
||||
writer.writeTo(file = storage.file, requestor = null, LineSeparator.LF, isUseXmlProlog)
|
||||
writer.writeTo(storage.file, requestor = null, LineSeparator.LF, isUseXmlProlog)
|
||||
}
|
||||
}.getOrLogException(LOG)
|
||||
}
|
||||
|
||||
@@ -1,7 +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("ReplaceJavaStaticMethodWithKotlinAnalog", "ReplaceGetOrSet")
|
||||
@file:OptIn(SettingsInternalApi::class)
|
||||
|
||||
package com.intellij.configurationStore
|
||||
|
||||
import com.intellij.codeWithMe.ClientId
|
||||
@@ -11,7 +9,6 @@ import com.intellij.ide.impl.runUnderModalProgressIfIsEdt
|
||||
import com.intellij.ide.plugins.PluginManager
|
||||
import com.intellij.ide.plugins.PluginManagerCore
|
||||
import com.intellij.notification.NotificationsManager
|
||||
import com.intellij.openapi.application.AppUIExecutor
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.application.readAction
|
||||
@@ -132,47 +129,37 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
return
|
||||
}
|
||||
|
||||
val componentInfo = createComponentInfo(
|
||||
component = component,
|
||||
stateSpec = null,
|
||||
serviceDescriptor = serviceDescriptor,
|
||||
pluginId = pluginId,
|
||||
)
|
||||
initComponent(info = componentInfo, changedStorages = null, reloadData = ThreeState.NO)
|
||||
val componentInfo = createComponentInfo(component, stateSpec = null, serviceDescriptor, pluginId)
|
||||
initComponent(componentInfo, changedStorages = null, reloadData = ThreeState.NO)
|
||||
}
|
||||
else {
|
||||
componentName = stateSpec.name
|
||||
val componentInfo = createComponentInfo(
|
||||
component = component,
|
||||
stateSpec = stateSpec,
|
||||
serviceDescriptor = serviceDescriptor,
|
||||
pluginId = pluginId,
|
||||
)
|
||||
val componentInfo = createComponentInfo(component, stateSpec, serviceDescriptor, pluginId)
|
||||
// still must be added to a component list to support explicit save later
|
||||
if (!stateSpec.allowLoadInTests && !(loadPolicy == StateLoadPolicy.LOAD ||
|
||||
(loadPolicy == StateLoadPolicy.LOAD_ONLY_DEFAULT && stateSpec.defaultStateAsResource))) {
|
||||
if (!stateSpec.allowLoadInTests &&
|
||||
!(loadPolicy == StateLoadPolicy.LOAD || (loadPolicy == StateLoadPolicy.LOAD_ONLY_DEFAULT && stateSpec.defaultStateAsResource))) {
|
||||
component.noStateLoaded()
|
||||
component.initializeComponent()
|
||||
registerComponent(name = componentName, info = componentInfo)
|
||||
registerComponent(componentName, componentInfo)
|
||||
return
|
||||
}
|
||||
|
||||
if (initComponent(info = componentInfo, changedStorages = null, reloadData = ThreeState.NO) && serviceDescriptor != null) {
|
||||
if (initComponent(componentInfo, changedStorages = null, reloadData = ThreeState.NO) && serviceDescriptor != null) {
|
||||
// if not service, so, component manager will check it later for all components
|
||||
val project = project
|
||||
if (project != null && project.isInitialized) {
|
||||
val app = ApplicationManager.getApplication()
|
||||
if (!app.isHeadlessEnvironment && !app.isUnitTestMode) {
|
||||
notifyUnknownMacros(store = this, project = project, componentName = componentName)
|
||||
notifyUnknownMacros(store = this, project, componentName = componentName)
|
||||
}
|
||||
}
|
||||
}
|
||||
registerComponent(name = componentName, info = componentInfo)
|
||||
registerComponent(componentName, componentInfo)
|
||||
}
|
||||
component.initializeComponent()
|
||||
}
|
||||
else if (loadPolicy == StateLoadPolicy.LOAD && component is com.intellij.openapi.util.JDOMExternalizable) {
|
||||
componentName = initJdom(component = component, pluginId = pluginId)
|
||||
componentName = initJdom(component, pluginId)
|
||||
}
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
@@ -182,50 +169,33 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
if (e is ControlFlowException) {
|
||||
throw e
|
||||
}
|
||||
LOG.error(PluginException("Cannot init component state " +
|
||||
"(componentName=$componentName, componentClass=${component.javaClass.simpleName})", e, pluginId))
|
||||
LOG.error(PluginException("Cannot init component state (componentName=$componentName, componentClass=${component.javaClass.simpleName})", e, pluginId))
|
||||
}
|
||||
}
|
||||
|
||||
private fun initJdom(@Suppress("DEPRECATION") component: com.intellij.openapi.util.JDOMExternalizable, pluginId: PluginId): String {
|
||||
if (component.javaClass.name !in ignoredDeprecatedJDomExternalizableComponents) {
|
||||
LOG.error(PluginException("""
|
||||
|Component ${component.javaClass.name} implements deprecated JDOMExternalizable interface to serialize its state.
|
||||
|IntelliJ Platform will stop supporting such components in the future, so it must be migrated to use PersistentStateComponent.
|
||||
|See https://plugins.jetbrains.com/docs/intellij/persisting-state-of-components.html for details.
|
||||
""".trimMargin(), pluginId))
|
||||
|Component ${component.javaClass.name} implements deprecated JDOMExternalizable interface to serialize its state.
|
||||
|IntelliJ Platform will stop supporting such components in the future, so it must be migrated to use PersistentStateComponent.
|
||||
|See https://plugins.jetbrains.com/docs/intellij/persisting-state-of-components.html for details.
|
||||
""".trimMargin(), pluginId))
|
||||
}
|
||||
|
||||
val componentName = getComponentName(component)
|
||||
val componentInfo = createComponentInfo(
|
||||
component = component,
|
||||
stateSpec = null,
|
||||
serviceDescriptor = null,
|
||||
pluginId = pluginId,
|
||||
)
|
||||
val element = storageManager.getOldStorage(
|
||||
component = component,
|
||||
componentName = componentName,
|
||||
operation = StateStorageOperation.READ,
|
||||
) ?.getState(
|
||||
component = component,
|
||||
componentName = componentName,
|
||||
pluginId = pluginId,
|
||||
stateClass = Element::class.java,
|
||||
mergeInto = null,
|
||||
reload = false,
|
||||
)
|
||||
val componentInfo = createComponentInfo(component, stateSpec = null, serviceDescriptor = null, pluginId = pluginId)
|
||||
val element = storageManager.getOldStorage(component, componentName, StateStorageOperation.READ)
|
||||
?.getState(component, componentName, pluginId, stateClass = Element::class.java, mergeInto = null, reload = false)
|
||||
if (element != null) {
|
||||
component.readExternal(element)
|
||||
}
|
||||
registerComponent(name = componentName, info = componentInfo)
|
||||
registerComponent(componentName, componentInfo)
|
||||
return componentName
|
||||
}
|
||||
|
||||
private fun getComponentName(component: Any): String {
|
||||
@Suppress("DEPRECATION")
|
||||
return if (component is NamedComponent) component.componentName else component.javaClass.name
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
private fun getComponentName(component: Any): String =
|
||||
if (component is NamedComponent) component.componentName else component.javaClass.name
|
||||
|
||||
override fun unloadComponent(component: Any) {
|
||||
@Suppress("DEPRECATION")
|
||||
@@ -239,26 +209,19 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
|
||||
final override fun initPersistencePlainComponent(component: Any, key: String, pluginId: PluginId) {
|
||||
val stateSpec = StateAnnotation(key, FileStorageAnnotation(StoragePathMacros.WORKSPACE_FILE, false))
|
||||
registerComponent(
|
||||
name = stateSpec.name,
|
||||
info = createComponentInfo(
|
||||
component = PersistenceStateAdapter(component),
|
||||
stateSpec = stateSpec,
|
||||
serviceDescriptor = null,
|
||||
pluginId = pluginId,
|
||||
),
|
||||
)
|
||||
val componentInfo = createComponentInfo(PersistenceStateAdapter(component), stateSpec, serviceDescriptor = null, pluginId)
|
||||
registerComponent(stateSpec.name, componentInfo)
|
||||
}
|
||||
|
||||
override suspend fun save(forceSavingAllSettings: Boolean) {
|
||||
val result = SaveResult()
|
||||
doSave(saveResult = result, forceSavingAllSettings = forceSavingAllSettings)
|
||||
result.rethrow()
|
||||
val saveResult = SaveResult()
|
||||
doSave(saveResult, forceSavingAllSettings)
|
||||
saveResult.rethrow()
|
||||
}
|
||||
|
||||
internal open suspend fun doSave(saveResult: SaveResult, forceSavingAllSettings: Boolean) {
|
||||
val saveSessionManager = createSaveSessionProducerManager()
|
||||
commitComponents(isForce = forceSavingAllSettings, sessionManager = saveSessionManager, saveResult = saveResult)
|
||||
commitComponents(forceSavingAllSettings, saveSessionManager, saveResult)
|
||||
saveSessionManager.save(saveResult)
|
||||
}
|
||||
|
||||
@@ -289,9 +252,11 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
}
|
||||
else {
|
||||
if (isSaveModLogEnabled) {
|
||||
SAVE_MOD_LOG.debug("Skip $name: was already saved in last " +
|
||||
"${TimeUnit.SECONDS.toMinutes(NOT_ROAMABLE_COMPONENT_SAVE_THRESHOLD_DEFAULT.toLong())} minutes " +
|
||||
"(lastSaved ${info.lastSaved}, now: $nowInSeconds)")
|
||||
SAVE_MOD_LOG.debug(
|
||||
"Skip $name: was already saved in last " +
|
||||
"${TimeUnit.SECONDS.toMinutes(NOT_ROAMABLE_COMPONENT_SAVE_THRESHOLD_DEFAULT.toLong())} minutes " +
|
||||
"(lastSaved ${info.lastSaved}, now: $nowInSeconds)"
|
||||
)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -313,13 +278,7 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
modificationCountChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
commitComponent(
|
||||
sessionManager = sessionManager,
|
||||
info = info,
|
||||
componentName = name,
|
||||
modificationCountChanged = modificationCountChanged,
|
||||
)
|
||||
commitComponent(sessionManager, info, name, modificationCountChanged)
|
||||
info.updateModificationCount(currentModificationCount)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
@@ -349,26 +308,17 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
val stateSpec = getStateSpec(component)
|
||||
LOG.debug { "saveComponent is called for ${stateSpec.name}" }
|
||||
val saveManager = createSaveSessionProducerManager()
|
||||
val storages = getStorageSpecs(component = component, stateSpec = stateSpec, operation = StateStorageOperation.WRITE)
|
||||
val storages = getStorageSpecs(component, stateSpec, StateStorageOperation.WRITE)
|
||||
val storage = storages.firstOrNull { !it.deprecated } ?: throw AssertionError("All storages are deprecated")
|
||||
val absolutePath = storageManager.expandMacro(storage.path).toString()
|
||||
|
||||
val componentInfo = components.get(stateSpec.name)
|
||||
|
||||
Disposer.newDisposable().use {
|
||||
VfsRootAccess.allowRootAccess(it, absolutePath)
|
||||
@Suppress("DEPRECATION")
|
||||
runUnderModalProgressIfIsEdt {
|
||||
commitComponent(
|
||||
sessionManager = saveManager,
|
||||
info = componentInfo ?: ComponentInfoImpl(
|
||||
pluginId = PluginManager.getPluginByClass(component::class.java)?.pluginId ?: PluginManagerCore.CORE_ID,
|
||||
component = component,
|
||||
stateSpec = stateSpec,
|
||||
),
|
||||
componentName = null,
|
||||
modificationCountChanged = false,
|
||||
)
|
||||
val pluginId = PluginManager.getPluginByClass(component::class.java)?.pluginId ?: PluginManagerCore.CORE_ID
|
||||
val componentInfo = componentInfo ?: ComponentInfoImpl(pluginId, component, stateSpec)
|
||||
commitComponent(saveManager, componentInfo, componentName = null, modificationCountChanged = false)
|
||||
val saveResult = SaveResult()
|
||||
saveManager.save(saveResult)
|
||||
saveResult.rethrow()
|
||||
@@ -376,9 +326,8 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
}
|
||||
}
|
||||
|
||||
internal open fun createSaveSessionProducerManager(): SaveSessionProducerManager {
|
||||
return SaveSessionProducerManager(isUseVfsForWrite = false, collectVfsEvents = false)
|
||||
}
|
||||
internal open fun createSaveSessionProducerManager(): SaveSessionProducerManager =
|
||||
SaveSessionProducerManager(isUseVfsForWrite = false, collectVfsEvents = false)
|
||||
|
||||
private suspend fun commitComponent(
|
||||
sessionManager: SaveSessionProducerManager,
|
||||
@@ -391,12 +340,7 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
if (component is com.intellij.openapi.util.JDOMExternalizable) {
|
||||
val effectiveComponentName = componentName ?: getComponentName(component)
|
||||
storageManager.getOldStorage(component, effectiveComponentName, StateStorageOperation.WRITE)?.let {
|
||||
sessionManager.getProducer(it)?.setState(
|
||||
component = component,
|
||||
componentName = effectiveComponentName,
|
||||
pluginId = info.pluginId,
|
||||
state = component,
|
||||
)
|
||||
sessionManager.getProducer(it)?.setState(component, effectiveComponentName, info.pluginId, component)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -410,11 +354,7 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
val stateStorageChooser = component as? StateStorageChooserEx
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val storageSpecs = getStorageSpecs(
|
||||
component = component as PersistentStateComponent<Any>,
|
||||
stateSpec = stateSpec,
|
||||
operation = StateStorageOperation.WRITE,
|
||||
)
|
||||
val storageSpecs = getStorageSpecs(component as PersistentStateComponent<Any>, stateSpec, StateStorageOperation.WRITE)
|
||||
for (storageSpec in storageSpecs) {
|
||||
var resolution = stateStorageChooser?.getResolution(storageSpec, StateStorageOperation.WRITE) ?: Resolution.DO
|
||||
if (resolution == Resolution.SKIP) {
|
||||
@@ -433,7 +373,7 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
val sessionProducer = sessionManager.getProducer(storage) ?: continue
|
||||
if (resolution == Resolution.CLEAR ||
|
||||
(storageSpec.deprecated && storageSpecs.none { !it.deprecated && it.value == storageSpec.value })) {
|
||||
sessionProducer.setState(component = component, componentName = effectiveComponentName, pluginId = info.pluginId, state = null)
|
||||
sessionProducer.setState(component, effectiveComponentName, info.pluginId, state = null)
|
||||
}
|
||||
else {
|
||||
if (!stateRequested) {
|
||||
@@ -445,25 +385,20 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
featureUsageSettingManager.logConfigurationChanged(effectiveComponentName, state)
|
||||
}
|
||||
|
||||
setStateToSaveSessionProducer(
|
||||
state = state,
|
||||
info = info,
|
||||
effectiveComponentName = effectiveComponentName,
|
||||
sessionProducer = sessionProducer,
|
||||
)
|
||||
setStateToSaveSessionProducer(state, info, effectiveComponentName, sessionProducer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// method is not called if storage is deprecated or clear was requested (state in these cases is null),
|
||||
// but called if state is null if returned so from a component
|
||||
// the method is not called if storage is deprecated or clear was requested (the state in these cases is `null`),
|
||||
// but is called if the state is `null` if returned so from a component
|
||||
protected open fun setStateToSaveSessionProducer(
|
||||
state: Any?,
|
||||
info: ComponentInfo,
|
||||
effectiveComponentName: String,
|
||||
sessionProducer: SaveSessionProducer,
|
||||
) {
|
||||
sessionProducer.setState(component = info.component, componentName = effectiveComponentName, pluginId = info.pluginId, state = state)
|
||||
sessionProducer.setState(info.component, effectiveComponentName, info.pluginId, state)
|
||||
}
|
||||
|
||||
private fun registerComponent(name: String, info: ComponentInfo): ComponentInfo? {
|
||||
@@ -474,7 +409,10 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
|
||||
val existing = components.putIfAbsent(name, info)
|
||||
if (existing != null && existing.component !== info.component) {
|
||||
LOG.error("Conflicting component name '$name': ${existing.component.javaClass} and ${info.component.javaClass} (componentManager=${storageManager.componentManager})")
|
||||
LOG.error(
|
||||
"Conflicting component name '$name': ${existing.component.javaClass} and " +
|
||||
"${info.component.javaClass} (componentManager=${storageManager.componentManager})"
|
||||
)
|
||||
return existing
|
||||
}
|
||||
else {
|
||||
@@ -486,11 +424,12 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val component = info.component as PersistentStateComponent<Any>
|
||||
if (info.stateSpec == null) {
|
||||
val configurationSchemaKey = info.configurationSchemaKey ?: throw UnsupportedOperationException("configurationSchemaKey must be specified for ${component.javaClass.name}")
|
||||
return initComponentWithoutStateSpec(component = component, configurationSchemaKey = configurationSchemaKey, pluginId = info.pluginId)
|
||||
val configurationSchemaKey = info.configurationSchemaKey ?:
|
||||
throw UnsupportedOperationException("configurationSchemaKey must be specified for ${component.javaClass.name}")
|
||||
return initComponentWithoutStateSpec(component, configurationSchemaKey, info.pluginId)
|
||||
}
|
||||
else {
|
||||
doInitComponent(info = info, component = component, changedStorages = changedStorages, reloadData = reloadData)
|
||||
doInitComponent(info, component, changedStorages, reloadData)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -501,19 +440,8 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
pluginId: PluginId,
|
||||
): Boolean {
|
||||
val stateClass = ComponentSerializationUtil.getStateClass<Any>(component.javaClass)
|
||||
val storage = getReadOnlyStorage(
|
||||
componentClass = component.javaClass,
|
||||
stateClass = stateClass,
|
||||
configurationSchemaKey = configurationSchemaKey,
|
||||
)
|
||||
val state = storage?.getState(
|
||||
component = component,
|
||||
componentName = "",
|
||||
pluginId = pluginId,
|
||||
stateClass = stateClass,
|
||||
mergeInto = null,
|
||||
reload = false,
|
||||
)
|
||||
val storage = getReadOnlyStorage(component.javaClass, stateClass, configurationSchemaKey)
|
||||
val state = storage?.getState(component, componentName = "", pluginId, stateClass, mergeInto = null, reload = false)
|
||||
if (state == null) {
|
||||
component.noStateLoaded()
|
||||
}
|
||||
@@ -523,9 +451,7 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
return true
|
||||
}
|
||||
|
||||
protected open fun getReadOnlyStorage(componentClass: Class<Any>, stateClass: Class<Any>, configurationSchemaKey: String): StateStorage? {
|
||||
return null
|
||||
}
|
||||
protected open fun getReadOnlyStorage(componentClass: Class<Any>, stateClass: Class<Any>, configurationSchemaKey: String): StateStorage? = null
|
||||
|
||||
private fun doInitComponent(
|
||||
info: ComponentInfo,
|
||||
@@ -542,14 +468,14 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
val stateSpec = info.stateSpec!!
|
||||
val name = stateSpec.name
|
||||
|
||||
// KT-39968: PathMacrosImpl could increase modCount on loadState, and the change has to be persisted
|
||||
// all other components follow a general rule: initial modCount is calculated after loadState phase
|
||||
// KT-39968: `PathMacrosImpl` could increase `modCount` on `loadState`, and the change has to be persisted;
|
||||
// all other components follow a general rule: initial `modCount` is calculated after the ` loadState ` phase
|
||||
val postLoadStateUpdateModificationCount = name != "PathMacrosImpl"
|
||||
|
||||
val defaultState = if (stateSpec.defaultStateAsResource) getDefaultState(component = component, componentName = name, stateClass = stateClass) else null
|
||||
val defaultState = if (stateSpec.defaultStateAsResource) getDefaultState(component, name, stateClass) else null
|
||||
if (loadPolicy == StateLoadPolicy.LOAD || info.stateSpec?.allowLoadInTests == true) {
|
||||
val storageChooser = component as? StateStorageChooserEx
|
||||
for (storageSpec in getStorageSpecs(component = component, stateSpec = stateSpec, operation = StateStorageOperation.READ)) {
|
||||
for (storageSpec in getStorageSpecs(component, stateSpec, StateStorageOperation.READ)) {
|
||||
if (storageChooser?.getResolution(storageSpec, StateStorageOperation.READ) == Resolution.SKIP) {
|
||||
continue
|
||||
}
|
||||
@@ -558,26 +484,19 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
|
||||
// if storage marked as changed, it means that analyzeExternalChangesAndUpdateIfNeeded was called for it and storage is already reloaded
|
||||
val isReloadDataForStorage = if (reloadData == ThreeState.UNSURE) {
|
||||
changedStorages == null || isStorageChanged(changedStorages = changedStorages, storage = storage)
|
||||
changedStorages == null || isStorageChanged(changedStorages, storage)
|
||||
}
|
||||
else {
|
||||
reloadData.toBoolean()
|
||||
}
|
||||
|
||||
val stateGetter = doCreateStateGetter(
|
||||
reloadData = isReloadDataForStorage,
|
||||
storage = storage,
|
||||
info = info,
|
||||
componentName = name,
|
||||
stateClass = stateClass,
|
||||
useLoadedStateAsExisting = stateSpec.useLoadedStateAsExisting,
|
||||
)
|
||||
val stateGetter = doCreateStateGetter(isReloadDataForStorage, storage, info, name, stateClass, stateSpec.useLoadedStateAsExisting)
|
||||
var state = stateGetter.getState(defaultState)
|
||||
if (state == null) {
|
||||
if (changedStorages != null && isStorageChanged(changedStorages, storage)) {
|
||||
// state will be null if file deleted
|
||||
// we must create empty (initial) state to reinit component
|
||||
state = deserializeState(stateElement = Element("state"), stateClass = stateClass)!!
|
||||
// the state will be `null` if a file is deleted;
|
||||
// we must create an empty (initial) state to reinit the component
|
||||
state = deserializeState(Element("state"), stateClass)!!
|
||||
}
|
||||
else {
|
||||
if (isReportStatisticAllowed(stateSpec, storageSpec)) {
|
||||
@@ -593,7 +512,7 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
component.loadState(state)
|
||||
val stateAfterLoad = stateGetter.archiveState()
|
||||
if (isReportStatisticAllowed(stateSpec, storageSpec)) {
|
||||
featureUsageSettingManager.logConfigurationState(componentName = name, state = stateAfterLoad ?: state)
|
||||
featureUsageSettingManager.logConfigurationState(name, stateAfterLoad ?: state)
|
||||
}
|
||||
|
||||
if (postLoadStateUpdateModificationCount) {
|
||||
@@ -619,9 +538,8 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun isReportStatisticAllowed(stateSpec: State, storageSpec: Storage): Boolean {
|
||||
return !storageSpec.deprecated && stateSpec.reportStatistic && storageSpec.value != StoragePathMacros.CACHE_FILE
|
||||
}
|
||||
protected open fun isReportStatisticAllowed(stateSpec: State, storageSpec: Storage): Boolean =
|
||||
!storageSpec.deprecated && stateSpec.reportStatistic && storageSpec.value != StoragePathMacros.CACHE_FILE
|
||||
|
||||
protected open fun doCreateStateGetter(
|
||||
reloadData: Boolean,
|
||||
@@ -636,34 +554,19 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
|
||||
// getting state after loading with an active controller can lead to unusual issues - disable write protection
|
||||
if (useLoadedStateAsExisting && storage is XmlElementStorage && (storage.controller == null || project != null) && isUseLoadedStateAsExisting(storage)) {
|
||||
return storage.createGetSession(
|
||||
component = component,
|
||||
componentName = componentName,
|
||||
pluginId = info.pluginId,
|
||||
stateClass = stateClass,
|
||||
reload = reloadData,
|
||||
)
|
||||
return storage.createGetSession(component, componentName, info.pluginId, stateClass, reloadData)
|
||||
}
|
||||
|
||||
return object : StateGetter<Any> {
|
||||
override fun getState(mergeInto: Any?): Any? {
|
||||
return storage.getState(
|
||||
component = component,
|
||||
componentName = componentName,
|
||||
pluginId = info.pluginId,
|
||||
stateClass = stateClass,
|
||||
mergeInto = mergeInto,
|
||||
reload = reloadData,
|
||||
)
|
||||
}
|
||||
override fun getState(mergeInto: Any?): Any? =
|
||||
storage.getState(component, componentName, info.pluginId, stateClass, mergeInto, reloadData)
|
||||
|
||||
override fun archiveState(): Any? = null
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun isUseLoadedStateAsExisting(storage: StateStorage): Boolean {
|
||||
return (storage as? XmlElementStorage)?.roamingType != RoamingType.DISABLED && isUseLoadedStateAsExistingVmProperty
|
||||
}
|
||||
protected open fun isUseLoadedStateAsExisting(storage: StateStorage): Boolean =
|
||||
(storage as? XmlElementStorage)?.roamingType != RoamingType.DISABLED && isUseLoadedStateAsExistingVmProperty
|
||||
|
||||
protected open fun getPathMacroManagerForDefaults(): PathMacroManager? = null
|
||||
|
||||
@@ -694,9 +597,9 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
if (stateSpec.defaultStateAsResource) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
throw AssertionError("No storage specified")
|
||||
}
|
||||
|
||||
return sortStoragesByDeprecated(storages)
|
||||
}
|
||||
|
||||
@@ -735,37 +638,40 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
}
|
||||
|
||||
override fun reloadStates(componentNames: Set<String>) {
|
||||
reinitComponents(componentNames = componentNames, changedStorages = emptySet(), notReloadableComponents = emptySet())
|
||||
reinitComponents(componentNames, changedStorages = emptySet(), notReloadableComponents = emptySet())
|
||||
}
|
||||
|
||||
internal fun batchReloadStates(componentNames: Set<String>, messageBus: MessageBus) {
|
||||
val publisher = messageBus.syncPublisher(BatchUpdateListener.TOPIC)
|
||||
publisher.onBatchUpdateStarted()
|
||||
try {
|
||||
reinitComponents(componentNames = componentNames, changedStorages = emptySet(), notReloadableComponents = emptySet())
|
||||
reinitComponents(componentNames, changedStorages = emptySet(), notReloadableComponents = emptySet())
|
||||
}
|
||||
finally {
|
||||
publisher.onBatchUpdateFinished()
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadPerClientState(componentClass: Class<out PersistentStateComponent<*>>,
|
||||
info: ComponentInfo,
|
||||
changedStorages: Set<StateStorage>) {
|
||||
private fun reloadPerClientState(
|
||||
componentClass: Class<out PersistentStateComponent<*>>,
|
||||
info: ComponentInfo,
|
||||
changedStorages: Set<StateStorage>
|
||||
) {
|
||||
if (ClientId.isCurrentlyUnderLocalId) {
|
||||
throw AssertionError("This method must be called under remote client id")
|
||||
}
|
||||
|
||||
val perClientComponent = (storageManager.componentManager ?: application).getService(componentClass)
|
||||
if (perClientComponent == null || perClientComponent === info.component) {
|
||||
LOG.error("Failed to reload per-client component '${info.stateSpec?.name ?: componentClass.simpleName}: " +
|
||||
"looks like it is not registered as a per-client service " +
|
||||
"(componentManager=${storageManager.componentManager})")
|
||||
LOG.error(
|
||||
"Failed to reload per-client component '${info.stateSpec?.name ?: componentClass.simpleName}: " +
|
||||
"looks like it is not registered as a per-client service (componentManager=${storageManager.componentManager})"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
val newInfo = ComponentInfoImpl(info.pluginId, perClientComponent, info.stateSpec)
|
||||
initComponent(info = newInfo, changedStorages = changedStorages.ifEmpty { null }, reloadData = ThreeState.YES)
|
||||
initComponent(newInfo, changedStorages.ifEmpty { null }, reloadData = ThreeState.YES)
|
||||
}
|
||||
|
||||
final override fun reloadState(componentClass: Class<out PersistentStateComponent<*>>) {
|
||||
@@ -777,7 +683,7 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
return
|
||||
}
|
||||
|
||||
initComponent(info = info, changedStorages = emptySet(), reloadData = ThreeState.YES)
|
||||
initComponent(info, changedStorages = emptySet(), reloadData = ThreeState.YES)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -794,7 +700,7 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
}
|
||||
|
||||
val isChangedStoragesEmpty = changedStorages.isEmpty()
|
||||
initComponent(info = info, changedStorages = if (isChangedStoragesEmpty) null else changedStorages, reloadData = ThreeState.UNSURE)
|
||||
initComponent(info, changedStorages = if (isChangedStoragesEmpty) null else changedStorages, reloadData = ThreeState.UNSURE)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -810,19 +716,19 @@ abstract class ComponentStoreImpl : IComponentStore {
|
||||
val componentNames = HashSet<String>()
|
||||
for (storage in changedStorages) {
|
||||
LOG.runAndLogException {
|
||||
// we must update (reload in-memory storage data) even if non-reloadable component is detected later
|
||||
// we must update (reload in-memory storage data) even if a non-reloadable component is detected later
|
||||
// not saved -> user does a modification -> new (on disk) state will be overwritten and not applied
|
||||
storage.analyzeExternalChangesAndUpdateIfNeeded(componentNames)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
if (componentNames.isEmpty()) {
|
||||
return emptySet()
|
||||
}
|
||||
LOG.debug { "Reload components: $componentNames" }
|
||||
|
||||
val notReloadableComponents = getNotReloadableComponents(componentNames)
|
||||
reinitComponents(componentNames = componentNames, changedStorages = changedStorages, notReloadableComponents = notReloadableComponents)
|
||||
reinitComponents(componentNames, changedStorages, notReloadableComponents)
|
||||
return notReloadableComponents.ifEmpty { null }
|
||||
}
|
||||
|
||||
@@ -861,6 +767,7 @@ interface ExternalStorageWithInternalPart {
|
||||
* Provides a way to temporarily ignore a known component extending deprecated JDOMExternalizable interface to avoid having unnecessary
|
||||
* errors in the log. Each entry must be accompanied by a link to the corresponding YouTrack issue.
|
||||
*/
|
||||
@Suppress("ReplaceJavaStaticMethodWithKotlinAnalog")
|
||||
private val ignoredDeprecatedJDomExternalizableComponents = java.util.Set.of(
|
||||
"jetbrains.buildServer.codeInspection.InspectionPassRegistrar", //TW-82189
|
||||
)
|
||||
@@ -889,7 +796,8 @@ private fun notifyUnknownMacros(store: IComponentStore, project: Project, compon
|
||||
}
|
||||
|
||||
val macros = LinkedHashSet(immutableMacros)
|
||||
AppUIExecutor.onUiThread().expireWith(project).submit {
|
||||
@Suppress("DEPRECATION")
|
||||
com.intellij.openapi.application.AppUIExecutor.onUiThread().expireWith(project).submit {
|
||||
var notified: MutableList<String>? = null
|
||||
val manager = NotificationsManager.getNotificationsManager()
|
||||
for (notification in manager.getNotificationsOfType(
|
||||
@@ -903,23 +811,21 @@ private fun notifyUnknownMacros(store: IComponentStore, project: Project, compon
|
||||
macros.removeAll(notified)
|
||||
}
|
||||
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
if (macros.isEmpty()) {
|
||||
return@submit
|
||||
}
|
||||
|
||||
LOG.debug("Reporting unknown path macros $macros in component $componentName")
|
||||
doNotify(macros = macros, project = project, substitutorToStore = java.util.Map.of(substitutor, store))
|
||||
doNotify(macros, project, substitutorToStore = java.util.Map.of(substitutor, store))
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend fun getStateForComponent(component: PersistentStateComponent<*>, stateSpec: State): Any? {
|
||||
return when {
|
||||
component is SerializablePersistentStateComponent<*> -> component.state
|
||||
stateSpec.getStateRequiresEdt -> withContext(Dispatchers.EDT) { component.state }
|
||||
else -> readAction { component.state }
|
||||
}
|
||||
internal suspend fun getStateForComponent(component: PersistentStateComponent<*>, stateSpec: State): Any? = when {
|
||||
component is SerializablePersistentStateComponent<*> -> component.state
|
||||
stateSpec.getStateRequiresEdt -> withContext(Dispatchers.EDT) { component.state }
|
||||
else -> readAction { component.state }
|
||||
}
|
||||
|
||||
private fun isStorageChanged(changedStorages: Set<StateStorage>, storage: StateStorage): Boolean {
|
||||
return changedStorages.contains(storage) || (storage is ExternalStorageWithInternalPart && changedStorages.contains(storage.internalStorage))
|
||||
}
|
||||
private fun isStorageChanged(changedStorages: Set<StateStorage>, storage: StateStorage): Boolean =
|
||||
changedStorages.contains(storage) || (storage is ExternalStorageWithInternalPart && changedStorages.contains(storage.internalStorage))
|
||||
|
||||
@@ -54,13 +54,9 @@ abstract class ComponentStoreWithExtraComponents : ComponentStoreImpl() {
|
||||
}
|
||||
|
||||
override suspend fun doSave(saveResult: SaveResult, forceSavingAllSettings: Boolean) {
|
||||
val saveSessionManager = createSaveSessionProducerManager()
|
||||
saveSettingsAndCommitComponents(
|
||||
saveResult = saveResult,
|
||||
forceSavingAllSettings = forceSavingAllSettings,
|
||||
sessionManager = saveSessionManager,
|
||||
)
|
||||
saveSessionManager.save(saveResult)
|
||||
val sessionManager = createSaveSessionProducerManager()
|
||||
saveSettingsAndCommitComponents(saveResult, forceSavingAllSettings, sessionManager)
|
||||
sessionManager.save(saveResult)
|
||||
}
|
||||
|
||||
internal suspend fun saveSettingsAndCommitComponents(
|
||||
@@ -74,9 +70,7 @@ abstract class ComponentStoreWithExtraComponents : ComponentStoreImpl() {
|
||||
try {
|
||||
settingsSavingComponent.save()
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: CancellationException) { throw e }
|
||||
catch (e: Throwable) {
|
||||
saveResult.addError(e)
|
||||
}
|
||||
@@ -86,40 +80,32 @@ abstract class ComponentStoreWithExtraComponents : ComponentStoreImpl() {
|
||||
|
||||
// SchemeManager (asyncSettingsSavingComponent) must be saved before saving components
|
||||
// (component state uses scheme manager in an ipr project, so, we must save it before) so, call it sequentially
|
||||
commitComponents(isForce = forceSavingAllSettings, sessionManager = sessionManager, saveResult = saveResult)
|
||||
commitComponents(forceSavingAllSettings, sessionManager, saveResult)
|
||||
}
|
||||
|
||||
final override suspend fun commitComponents(isForce: Boolean, sessionManager: SaveSessionProducerManager, saveResult: SaveResult) {
|
||||
// ensure that this task will not interrupt regular saving
|
||||
runCatching {
|
||||
commitObsoleteComponents(session = sessionManager, isProjectLevel = false)
|
||||
commitObsoleteComponents(sessionManager, isProjectLevel = false)
|
||||
}.getOrLogException(LOG)
|
||||
super.commitComponents(isForce = isForce, sessionManager = sessionManager, saveResult = saveResult)
|
||||
super.commitComponents(isForce, sessionManager, saveResult)
|
||||
}
|
||||
|
||||
internal open fun commitObsoleteComponents(session: SaveSessionProducerManager, isProjectLevel: Boolean) {
|
||||
val storageManager = storageManager as? StateStorageManagerImpl ?: return
|
||||
for (item in ObsoleteStorageBean.EP_NAME.filterableLazySequence()) {
|
||||
val bean = item.instance ?: continue
|
||||
if (bean.isProjectLevel != isProjectLevel) {
|
||||
continue
|
||||
}
|
||||
|
||||
val storage = storageManager.getOrCreateStorage(collapsedPath = bean.file ?: continue, roamingType = RoamingType.DISABLED)
|
||||
if (bean.isProjectLevel != isProjectLevel) continue
|
||||
val collapsedPath = bean.file ?: continue
|
||||
val storage = storageManager.getOrCreateStorage(collapsedPath, roamingType = RoamingType.DISABLED)
|
||||
for (componentName in bean.components) {
|
||||
session.getProducer(storage)?.setState(
|
||||
component = null,
|
||||
componentName = componentName,
|
||||
pluginId = item.pluginDescriptor.pluginId,
|
||||
state = null,
|
||||
)
|
||||
session.getProducer(storage)?.setState(component = null, componentName, item.pluginDescriptor.pluginId, state = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final override fun release() {
|
||||
asyncSettingsSavingComponents.drop()
|
||||
|
||||
super.release()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ open class DirectoryBasedStorage(
|
||||
val dir = if (useVfs) {
|
||||
var dir = storage.getVirtualFile()
|
||||
if (dir == null || !dir.exists()) {
|
||||
dir = createDir(storage.dir, this)
|
||||
dir = createDir(storage.dir, requestor = this)
|
||||
storage.cachedVirtualFile = dir
|
||||
}
|
||||
dir
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("ReplacePutWithAssignment", "ReplaceGetOrSet")
|
||||
|
||||
package com.intellij.configurationStore
|
||||
|
||||
import com.intellij.ide.highlighter.ProjectFileType
|
||||
@@ -112,7 +110,7 @@ open class ProjectStoreImpl(final override val project: Project) : ComponentStor
|
||||
macros.add(Macro(StoragePathMacros.WORKSPACE_FILE, workspacePath))
|
||||
|
||||
if (isUnitTestMode) {
|
||||
// we don't load default state in tests as app store does, because:
|
||||
// we don't load the default state in tests as the app store does, because:
|
||||
// 1) we should not do it
|
||||
// 2) it was so before, so, we preserve the old behavior (otherwise RunManager will load template run configurations)
|
||||
// load state only if there are existing files
|
||||
@@ -211,14 +209,13 @@ open class ProjectStoreImpl(final override val project: Project) : ComponentStor
|
||||
return "$prefix${Integer.toHexString(path.invariantSeparatorsPathString.hashCode())}"
|
||||
}
|
||||
|
||||
final override fun getPresentableUrl(): String {
|
||||
final override fun getPresentableUrl(): String =
|
||||
if (isDirectoryBased) {
|
||||
return (dirOrFile ?: throw IllegalStateException("setPath was not yet called")).invariantSeparatorsPathString
|
||||
(dirOrFile ?: throw IllegalStateException("setPath was not yet called")).invariantSeparatorsPathString
|
||||
}
|
||||
else {
|
||||
return projectFilePath.invariantSeparatorsPathString
|
||||
projectFilePath.invariantSeparatorsPathString
|
||||
}
|
||||
}
|
||||
|
||||
final override fun getProjectWorkspaceId(): String? = ProjectIdManager.getInstance(project).id
|
||||
|
||||
@@ -388,8 +385,7 @@ open class ProjectStoreImpl(final override val project: Project) : ComponentStor
|
||||
saveResult: SaveResult,
|
||||
forceSavingAllSettings: Boolean,
|
||||
projectSessionManager: ProjectSaveSessionProducerManager
|
||||
) {
|
||||
}
|
||||
) { }
|
||||
|
||||
override fun createSaveSessionProducerManager(): ProjectSaveSessionProducerManager = ProjectSaveSessionProducerManager(project, storageManager.isUseVfsForWrite)
|
||||
|
||||
@@ -411,33 +407,24 @@ private class ProjectStateStorageManager(private val project: Project) : StateSt
|
||||
|
||||
override fun normalizeFileSpec(fileSpec: String): String = removeMacroIfStartsWith(path = super.normalizeFileSpec(fileSpec), macro = PROJECT_CONFIG_DIR)
|
||||
|
||||
override fun expandMacro(collapsedPath: String): Path {
|
||||
if (collapsedPath[0] == '$') {
|
||||
return super.expandMacro(collapsedPath)
|
||||
}
|
||||
else {
|
||||
// PROJECT_CONFIG_DIR is the first macro
|
||||
return macros.get(0).value.resolve(collapsedPath)
|
||||
}
|
||||
}
|
||||
override fun expandMacro(collapsedPath: String): Path =
|
||||
if (collapsedPath[0] == '$') super.expandMacro(collapsedPath)
|
||||
else macros[0].value.resolve(collapsedPath) // PROJECT_CONFIG_DIR is the first macro
|
||||
|
||||
override fun beforeElementSaved(elements: MutableList<Element>, rootAttributes: MutableMap<String, String>) {
|
||||
rootAttributes.put(VERSION_OPTION, "4")
|
||||
}
|
||||
|
||||
override fun getOldStorageSpec(component: Any, componentName: String, operation: StateStorageOperation): String {
|
||||
return if (ComponentManagerImpl.badWorkspaceComponents.contains(componentName)) StoragePathMacros.WORKSPACE_FILE else PROJECT_FILE
|
||||
}
|
||||
override fun getOldStorageSpec(component: Any, componentName: String, operation: StateStorageOperation): String =
|
||||
if (ComponentManagerImpl.badWorkspaceComponents.contains(componentName)) StoragePathMacros.WORKSPACE_FILE else PROJECT_FILE
|
||||
|
||||
override val isExternalSystemStorageEnabled: Boolean
|
||||
get() = project.isExternalStorageEnabled
|
||||
}
|
||||
|
||||
@CalledInAny
|
||||
internal suspend fun ensureFilesWritable(project: Project, files: Collection<VirtualFile>): ReadonlyStatusHandler.OperationStatus {
|
||||
return withContext(Dispatchers.EDT) {
|
||||
ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(files)
|
||||
}
|
||||
internal suspend fun ensureFilesWritable(project: Project, files: Collection<VirtualFile>): ReadonlyStatusHandler.OperationStatus = withContext(Dispatchers.EDT) {
|
||||
ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(files)
|
||||
}
|
||||
|
||||
internal val useBackgroundSave: Boolean
|
||||
|
||||
@@ -43,22 +43,7 @@ internal open class SaveSessionProducerManager(private val isUseVfsForWrite: Boo
|
||||
if (isUseVfsForWrite) {
|
||||
writeAction {
|
||||
for (saveSession in saveSessions) {
|
||||
try {
|
||||
saveSession.saveBlocking()
|
||||
}
|
||||
catch (e: ReadOnlyModificationException) {
|
||||
LOG.warn(e)
|
||||
saveResult.addReadOnlyFile(SaveSessionAndFile(e.session ?: saveSession, e.file))
|
||||
}
|
||||
catch (e: ProcessCanceledException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Exception) {
|
||||
saveResult.addError(e)
|
||||
}
|
||||
saveSessionBlocking(saveSession, saveResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,22 +51,7 @@ internal open class SaveSessionProducerManager(private val isUseVfsForWrite: Boo
|
||||
val events = if (collectVfsEvents) ArrayList<VFileEvent>() else null
|
||||
val syncList = if (events == null) null else Collections.synchronizedList(events)
|
||||
for (saveSession in saveSessions) {
|
||||
try {
|
||||
saveSession.save(syncList)
|
||||
}
|
||||
catch (e: ReadOnlyModificationException) {
|
||||
LOG.warn(e)
|
||||
saveResult.addReadOnlyFile(SaveSessionAndFile(e.session ?: saveSession, e.file))
|
||||
}
|
||||
catch (e: ProcessCanceledException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Exception) {
|
||||
saveResult.addError(e)
|
||||
}
|
||||
saveSession(saveSession, syncList, saveResult)
|
||||
}
|
||||
if (!events.isNullOrEmpty()) {
|
||||
blockingContext {
|
||||
@@ -90,4 +60,34 @@ internal open class SaveSessionProducerManager(private val isUseVfsForWrite: Boo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveSessionBlocking(saveSession: SaveSession, saveResult: SaveResult) {
|
||||
try {
|
||||
saveSession.saveBlocking()
|
||||
}
|
||||
catch (e: ReadOnlyModificationException) {
|
||||
LOG.warn(e)
|
||||
saveResult.addReadOnlyFile(SaveSessionAndFile(e.session ?: saveSession, e.file))
|
||||
}
|
||||
catch (e: ProcessCanceledException) { throw e }
|
||||
catch (e: CancellationException) { throw e }
|
||||
catch (e: Exception) {
|
||||
saveResult.addError(e)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveSession(saveSession: SaveSession, events: MutableList<VFileEvent>?, saveResult: SaveResult) {
|
||||
try {
|
||||
saveSession.save(events)
|
||||
}
|
||||
catch (e: ReadOnlyModificationException) {
|
||||
LOG.warn(e)
|
||||
saveResult.addReadOnlyFile(SaveSessionAndFile(e.session ?: saveSession, e.file))
|
||||
}
|
||||
catch (e: ProcessCanceledException) { throw e }
|
||||
catch (e: CancellationException) { throw e }
|
||||
catch (e: Exception) {
|
||||
saveResult.addError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// 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")
|
||||
|
||||
package com.intellij.configurationStore.schemeManager
|
||||
|
||||
import com.intellij.configurationStore.LazySchemeProcessor
|
||||
@@ -8,6 +6,7 @@ import com.intellij.configurationStore.SchemeContentChangedHandler
|
||||
import com.intellij.openapi.options.Scheme
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import java.util.function.Function
|
||||
|
||||
internal sealed interface SchemeChangeEvent<T : Scheme, M : T> {
|
||||
@@ -18,24 +17,16 @@ internal sealed interface SchemeAddOrUpdateEvent {
|
||||
val file: VirtualFile
|
||||
}
|
||||
|
||||
private fun <T : Scheme, M : T> findExternalizableSchemeByFileName(fileName: String, schemeManager: SchemeManagerImpl<T, M>): T? {
|
||||
return schemeManager.schemes.firstOrNull {
|
||||
fileName == getSchemeFileName(schemeManager, it)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <T : Scheme, M : T> getSchemeFileName(schemeManager: SchemeManagerImpl<T, M>, scheme: T): String {
|
||||
return "${schemeManager.getFileName(scheme)}${schemeManager.schemeExtension}"
|
||||
}
|
||||
internal fun <T : Scheme, M : T> getSchemeFileName(schemeManager: SchemeManagerImpl<T, M>, scheme: T): String =
|
||||
"${schemeManager.getFileName(scheme)}${schemeManager.schemeExtension}"
|
||||
|
||||
internal fun <T : Scheme, M : T> readSchemeFromFile(file: VirtualFile, schemeLoader: SchemeLoader<T, M>, schemeManager: SchemeManagerImpl<T, M>): T? {
|
||||
val fileName = file.name
|
||||
if (file.isDirectory || !schemeManager.canRead(fileName)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return catchAndLog({ file.path }) {
|
||||
schemeLoader.loadScheme(fileName = fileName, input = null, preloadedBytes = file.contentsToByteArray())
|
||||
schemeLoader.loadScheme(fileName, input = null, file.contentsToByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +59,7 @@ internal class SchemeChangeApplicator<T : Scheme, M : T>(private val schemeManag
|
||||
val fileName = file.name
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val changedScheme = findExternalizableSchemeByFileName(fileName, schemeManager) as M?
|
||||
val changedScheme = schemeManager.schemes.firstOrNull<T> { getSchemeFileName(schemeManager, it) == fileName } as M?
|
||||
if (callSchemeContentChangedIfSupported(changedScheme, fileName, file, schemeManager)) {
|
||||
continue
|
||||
}
|
||||
@@ -108,7 +99,7 @@ internal class SchemeChangeApplicator<T : Scheme, M : T>(private val schemeManag
|
||||
}
|
||||
}
|
||||
|
||||
// exposed for test only
|
||||
@VisibleForTesting
|
||||
internal fun <T : Scheme, M : T> sortSchemeChangeEvents(inputEvents: Collection<SchemeChangeEvent<T, M>>): Collection<SchemeChangeEvent<T, M>> {
|
||||
if (inputEvents.size < 2) {
|
||||
return inputEvents
|
||||
@@ -116,13 +107,12 @@ internal fun <T : Scheme, M : T> sortSchemeChangeEvents(inputEvents: Collection<
|
||||
|
||||
var isThereSomeRemoveEvent = false
|
||||
|
||||
|
||||
val existingAddOrUpdate = HashSet<String>()
|
||||
val removedFileNames = HashSet<String>()
|
||||
val result = ArrayList(inputEvents)
|
||||
// first, remove any event before RemoveAllSchemes and remove RemoveScheme event if there is any subsequent add/update
|
||||
// first, remove any event before `RemoveAllSchemes` and remove `RemoveScheme` events if there is any subsequent add/update
|
||||
for (i in (result.size - 1) downTo 0) {
|
||||
val event = result.get(i)
|
||||
val event = result[i]
|
||||
if (event is RemoveAllSchemes) {
|
||||
for (j in (i - 1) downTo 0) {
|
||||
result.removeAt(j)
|
||||
@@ -152,8 +142,8 @@ internal fun <T : Scheme, M : T> sortSchemeChangeEvents(inputEvents: Collection<
|
||||
fun weight(event: SchemeChangeEvent<T, M>): Int = if (event is SchemeAddOrUpdateEvent) 1 else 0
|
||||
|
||||
if (isThereSomeRemoveEvent) {
|
||||
// second, move all RemoveScheme to first place, to ensure that SchemeLoader will be not created during processing of RemoveScheme event
|
||||
// (because RemoveScheme removes schemes from scheme manager directly)
|
||||
// second, move all `RemoveScheme` events to the top - to ensure that `SchemeLoader` won't be created during processing of `RemoveScheme` events
|
||||
// (because `RemoveScheme` removes schemes from the scheme manager directly)
|
||||
result.sortWith(Comparator { o1, o2 ->
|
||||
weight(o1) - weight(o2)
|
||||
})
|
||||
@@ -162,10 +152,12 @@ internal fun <T : Scheme, M : T> sortSchemeChangeEvents(inputEvents: Collection<
|
||||
return result
|
||||
}
|
||||
|
||||
private fun <T : Scheme, M : T> callSchemeContentChangedIfSupported(changedScheme: M?,
|
||||
fileName: String,
|
||||
file: VirtualFile,
|
||||
schemeManager: SchemeManagerImpl<T, M>): Boolean {
|
||||
private fun <T : Scheme, M : T> callSchemeContentChangedIfSupported(
|
||||
changedScheme: M?,
|
||||
fileName: String,
|
||||
file: VirtualFile,
|
||||
schemeManager: SchemeManagerImpl<T, M>
|
||||
): Boolean {
|
||||
if (changedScheme == null || schemeManager.processor !is SchemeContentChangedHandler<*> || schemeManager.processor !is LazySchemeProcessor) {
|
||||
return false
|
||||
}
|
||||
@@ -179,13 +171,10 @@ private fun <T : Scheme, M : T> callSchemeContentChangedIfSupported(changedSchem
|
||||
val schemeName = name
|
||||
?: schemeManager.processor.getSchemeKey(attributeProvider, FileUtilRt.getNameWithoutExtension(fileName))
|
||||
?: throw nameIsMissed(bytes)
|
||||
|
||||
val dataHolder = SchemeDataHolderImpl(schemeManager.processor, bytes, externalInfo)
|
||||
|
||||
val processor = schemeManager.processor
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(processor as SchemeContentChangedHandler<M>).schemeContentChanged(changedScheme, schemeName, dataHolder)
|
||||
(schemeManager.processor as SchemeContentChangedHandler<M>).schemeContentChanged(changedScheme, schemeName, dataHolder)
|
||||
}
|
||||
true
|
||||
} ?: false
|
||||
}
|
||||
} == true
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ internal class SchemeFileTracker<T : Scheme, M : T>(
|
||||
else if (schemeManager.canRead(event.childName) && isMyDirectory(event.parent)) {
|
||||
val virtualFile = event.file
|
||||
LOG.debug { "CREATED ${event.path} (virtualFile: ${if (virtualFile == null) "not " else ""}found)" }
|
||||
virtualFile?.let {
|
||||
list.add(AddScheme(it))
|
||||
if (virtualFile != null) {
|
||||
list.add(AddScheme(virtualFile))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,10 @@ import com.intellij.util.addSuppressed
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.nio.file.Path
|
||||
|
||||
@NonNls const val ROOT_CONFIG: String = "\$ROOT_CONFIG$"
|
||||
const val ROOT_CONFIG: String = "\$ROOT_CONFIG\$"
|
||||
|
||||
internal typealias FileChangeSubscriber = (schemeManager: SchemeManagerImpl<*, *>) -> Unit
|
||||
|
||||
@@ -59,11 +58,11 @@ sealed class SchemeManagerFactoryBase : SchemeManagerFactory(), SettingsSavingCo
|
||||
processor,
|
||||
streamProvider ?: componentManager?.stateStore?.storageManager?.streamProvider,
|
||||
ioDirectory = directoryPath ?: pathToFile(path),
|
||||
roamingType = roamingType,
|
||||
presentableName = presentableName,
|
||||
schemeNameToFileName = schemeNameToFileName,
|
||||
fileChangeSubscriber = fileChangeSubscriber,
|
||||
settingsCategory = settingsCategory,
|
||||
roamingType,
|
||||
presentableName,
|
||||
schemeNameToFileName,
|
||||
fileChangeSubscriber,
|
||||
settingsCategory,
|
||||
)
|
||||
if (isAutoSave) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -91,12 +90,8 @@ sealed class SchemeManagerFactoryBase : SchemeManagerFactory(), SettingsSavingCo
|
||||
try {
|
||||
processor(manager)
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: ProcessCanceledException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: CancellationException) { throw e }
|
||||
catch (e: ProcessCanceledException) { throw e }
|
||||
catch (e: Throwable) {
|
||||
LOG.error("Cannot reload settings for ${manager.javaClass.name}", e)
|
||||
}
|
||||
@@ -111,12 +106,8 @@ sealed class SchemeManagerFactoryBase : SchemeManagerFactory(), SettingsSavingCo
|
||||
try {
|
||||
registeredManager.saveImpl(events)
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: ProcessCanceledException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: CancellationException) { throw e }
|
||||
catch (e: ProcessCanceledException) { throw e }
|
||||
catch (e: Throwable) {
|
||||
error = addSuppressed(error, e)
|
||||
}
|
||||
@@ -153,20 +144,17 @@ sealed class SchemeManagerFactoryBase : SchemeManagerFactory(), SettingsSavingCo
|
||||
return path
|
||||
}
|
||||
|
||||
override fun pathToFile(path: String): Path {
|
||||
return ApplicationManager.getApplication().stateStore.storageManager.expandMacro(ROOT_CONFIG).resolve(path)
|
||||
}
|
||||
override fun pathToFile(path: String): Path =
|
||||
ApplicationManager.getApplication().stateStore.storageManager.expandMacro(ROOT_CONFIG).resolve(path)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
private class ProjectSchemeManagerFactory(private val project: Project) : SchemeManagerFactoryBase() {
|
||||
override val componentManager = project
|
||||
|
||||
override fun createFileChangeSubscriber(): FileChangeSubscriber {
|
||||
return { schemeManager ->
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode || project.getUserData(LISTEN_SCHEME_VFS_CHANGES_IN_TEST_MODE) == true) {
|
||||
project.messageBus.simpleConnect().subscribe(VirtualFileManager.VFS_CHANGES, SchemeFileTracker(schemeManager, project))
|
||||
}
|
||||
override fun createFileChangeSubscriber(): FileChangeSubscriber = { schemeManager ->
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode || project.getUserData(LISTEN_SCHEME_VFS_CHANGES_IN_TEST_MODE) == true) {
|
||||
project.messageBus.simpleConnect().subscribe(VirtualFileManager.VFS_CHANGES, SchemeFileTracker(schemeManager, project))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,11 +166,10 @@ sealed class SchemeManagerFactoryBase : SchemeManagerFactory(), SettingsSavingCo
|
||||
|
||||
val projectStore = project.stateStore as? IProjectStore
|
||||
val projectFileDir = projectStore?.directoryStorePath
|
||||
if (projectFileDir == null) {
|
||||
return if (projectStore != null) projectStore.projectBasePath.resolve(".$path") else Path.of(project.basePath!!, ".$path")
|
||||
}
|
||||
else {
|
||||
return projectFileDir.resolve(path)
|
||||
return when {
|
||||
projectFileDir != null -> projectFileDir.resolve(path)
|
||||
projectStore != null -> projectStore.projectBasePath.resolve(".$path")
|
||||
else -> Path.of(project.basePath!!, ".${path}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
get() = schemes.isEmpty()
|
||||
|
||||
private fun refreshVirtualDirectory() {
|
||||
// store refreshes root directory, so, we don't need to use refreshAndFindFile
|
||||
val directory = LocalFileSystem.getInstance().findFileByPath(ioDirectory.invariantSeparatorsPathString) ?: return
|
||||
// a parent component store refreshes the root directory, so we don't have to use `refreshAndFind*`
|
||||
val directory = LocalFileSystem.getInstance().findFileByNioFile(ioDirectory) ?: return
|
||||
cachedVirtualDirectory = directory
|
||||
directory.children
|
||||
(directory as? NewVirtualFile)?.markDirty()
|
||||
@@ -120,9 +120,7 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
val schemeKey = provider.schemeKey
|
||||
|
||||
val fileNameWithoutExtension = schemeNameToFileName(schemeKey)
|
||||
val externalInfo = ExternalInfo(fileNameWithoutExtension = fileNameWithoutExtension,
|
||||
fileExtension = fileNameWithoutExtension + ComponentStorageUtil.DEFAULT_EXT)
|
||||
|
||||
val externalInfo = ExternalInfo(fileNameWithoutExtension, fileExtension = fileNameWithoutExtension + ComponentStorageUtil.DEFAULT_EXT)
|
||||
externalInfo.schemeKey = schemeKey
|
||||
|
||||
val scheme = provider.createScheme()
|
||||
@@ -145,26 +143,18 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
|
||||
override fun loadBundledScheme(resourceName: String, requestor: Any?, pluginDescriptor: PluginDescriptor?): T? {
|
||||
try {
|
||||
val bytes = loadBytes(pluginDescriptor = pluginDescriptor, requestor = requestor, resourceName = resourceName) ?: return null
|
||||
lazyPreloadScheme(bytes = bytes, isOldSchemeNaming = isOldSchemeNaming) { name, parser ->
|
||||
val bytes = loadBytes(pluginDescriptor, requestor, resourceName) ?: return null
|
||||
lazyPreloadScheme(bytes, isOldSchemeNaming) { name, parser ->
|
||||
val attributeProvider: (String) -> String? = { parser.getAttributeValue(null, it) }
|
||||
val fileName = PathUtilRt.getFileName(resourceName)
|
||||
val extension = getFileExtension(fileName = fileName, isAllowAny = true)
|
||||
val externalInfo = ExternalInfo(fileNameWithoutExtension = fileName.substring(0, fileName.length - extension.length),
|
||||
fileExtension = extension)
|
||||
|
||||
val extension = getFileExtension(fileName, isAllowAny = true)
|
||||
val externalInfo = ExternalInfo(fileNameWithoutExtension = fileName.substring(0, fileName.length - extension.length), extension)
|
||||
val schemeKey = name
|
||||
?: (processor as LazySchemeProcessor).getSchemeKey(attributeProvider, externalInfo.fileNameWithoutExtension)
|
||||
?: throw nameIsMissed(bytes)
|
||||
|
||||
externalInfo.schemeKey = schemeKey
|
||||
|
||||
val scheme = (processor as LazySchemeProcessor).createScheme(dataHolder = SchemeDataHolderImpl(processor = processor,
|
||||
bytes = bytes,
|
||||
externalInfo = externalInfo),
|
||||
name = schemeKey,
|
||||
attributeProvider = attributeProvider,
|
||||
isBundled = true)
|
||||
val dataHolder = SchemeDataHolderImpl(processor, bytes, externalInfo)
|
||||
val scheme = (processor as LazySchemeProcessor).createScheme(dataHolder, schemeKey, attributeProvider, isBundled = true)
|
||||
val oldInfo = schemeListManager.data.putSchemeInfo(scheme, externalInfo)
|
||||
LOG.assertTrue(oldInfo == null)
|
||||
val oldScheme = schemeListManager.readOnlyExternalizableSchemes.put(schemeKey, scheme)
|
||||
@@ -183,9 +173,7 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun loadBytes(pluginDescriptor: PluginDescriptor?,
|
||||
requestor: Any?,
|
||||
resourceName: String): ByteArray? {
|
||||
private fun loadBytes(pluginDescriptor: PluginDescriptor?, requestor: Any?, resourceName: String): ByteArray? {
|
||||
val bytes: ByteArray?
|
||||
if (pluginDescriptor == null) {
|
||||
when (requestor) {
|
||||
@@ -200,8 +188,8 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
bytes = ResourceUtil.getResourceAsBytes(resourceName.removePrefix("/"),
|
||||
(if (requestor is ClassLoader) requestor else requestor!!.javaClass.classLoader))
|
||||
val classLoader = if (requestor is ClassLoader) requestor else requestor!!.javaClass.classLoader
|
||||
bytes = ResourceUtil.getResourceAsBytes(resourceName.removePrefix("/"), classLoader)
|
||||
if (bytes == null) {
|
||||
LOG.error("Cannot read scheme from $resourceName")
|
||||
return null
|
||||
@@ -224,9 +212,9 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
val filesToDelete = HashSet(filesToDelete)
|
||||
// the caller must call SchemeLoader.apply to bring back scheduled for deleting files
|
||||
this.filesToDelete.removeAll(filesToDelete)
|
||||
// `SchemeLoader` can use a retained list to bring back previously scheduled for deleting file;
|
||||
// but what if someone calls `save()` during a load and file will be deleted, although it should be loaded by a new load session
|
||||
// (because modified on disk)?
|
||||
// `SchemeLoader` can use a retained list to bring back a previously scheduled for deleting file;
|
||||
// but what if someone calls `save()` during a load and the file will be deleted,
|
||||
// although it should be loaded by a new load session (because modified on disk)?
|
||||
return SchemeLoader(schemeManager = this, oldList = schemeListManager.data, filesToDelete, isDuringLoad)
|
||||
}
|
||||
|
||||
@@ -243,8 +231,8 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
}
|
||||
|
||||
try {
|
||||
// isDuringLoad is true even if loadSchemes called not first time, but on reload,
|
||||
// because scheme processor should use cumulative event `reloaded` to update runtime state/caches
|
||||
// `isDuringLoad` is `true` even if `loadSchemes` called not first time, but on reload,
|
||||
// because a scheme processor should use a cumulative `reloaded` event to update runtime state/caches
|
||||
val schemeLoader = createSchemeLoader(isDuringLoad = true)
|
||||
val isLoadOnlyFromProvider = provider != null && provider.processChildren(fileSpec, roamingType, { canRead(it) }) { name, input, readOnly ->
|
||||
catchAndLog({ "${provider.javaClass.name}: $name" }) {
|
||||
@@ -293,7 +281,7 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
override fun reload(retainFilter: ((scheme: T) -> Boolean)?) {
|
||||
processor.beforeReloaded(this)
|
||||
// we must not remove non-persistent (e.g., predefined) schemes, because we cannot load it (obviously)
|
||||
// do not schedule scheme file removing because we just need to update our runtime state, not state on disk
|
||||
// do not schedule the scheme file removing because we just need to update our runtime state, not state on disk
|
||||
removeExternalizableSchemesFromRuntimeState()
|
||||
processor.reloaded(this, loadSchemes())
|
||||
}
|
||||
@@ -326,6 +314,7 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
processor.onSchemeDeleted(scheme as MUTABLE_SCHEME)
|
||||
}
|
||||
|
||||
retainExternalInfo(isScheduleToDelete = false, schemeToInfo = list.schemeToInfo, newSchemes = list.list)
|
||||
}
|
||||
|
||||
@@ -389,7 +378,7 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
this.filesToDelete.removeAll(filesToDelete)
|
||||
deleteFiles(errorCollector, filesToDelete, events)
|
||||
|
||||
// remove empty directory only if some file was deleted - avoid check on each save
|
||||
// remove an empty directory only if some file was deleted - avoid check on each save
|
||||
if (!hasSchemes && (provider == null || !provider.isApplicable(fileSpec, roamingType))) {
|
||||
removeDirectoryIfEmpty(errorCollector, events)
|
||||
}
|
||||
@@ -454,11 +443,10 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
externalInfo.isDigestEquals(newDigest)) {
|
||||
return
|
||||
}
|
||||
else if (isEqualToBundledScheme(externalInfo = externalInfo, newDigest = newDigest, scheme = scheme, filesToDelete = filesToDelete)) {
|
||||
return
|
||||
|
||||
else if (isEqualToBundledScheme(externalInfo, newDigest, scheme, filesToDelete)) {
|
||||
// we must check it only here to avoid deleting an old scheme just because it is empty
|
||||
// (an old idea save -> a new idea deletes on open)
|
||||
// (an old version saves -> a new version deletes)
|
||||
return
|
||||
}
|
||||
else if (processor is LazySchemeProcessor && processor.isSchemeDefault(scheme, newDigest)) {
|
||||
externalInfo?.scheduleDelete(filesToDelete, "equals to default")
|
||||
@@ -529,15 +517,17 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
externalInfo.schemeKey = processor.getSchemeKey(scheme)
|
||||
}
|
||||
|
||||
private fun isEqualToBundledScheme(externalInfo: ExternalInfo?,
|
||||
newDigest: Long,
|
||||
scheme: MUTABLE_SCHEME,
|
||||
filesToDelete: MutableSet<String>): Boolean {
|
||||
private fun isEqualToBundledScheme(
|
||||
externalInfo: ExternalInfo?,
|
||||
newDigest: Long,
|
||||
scheme: MUTABLE_SCHEME,
|
||||
filesToDelete: MutableSet<String>
|
||||
): Boolean {
|
||||
fun serializeIfPossible(scheme: T): Element? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val bundledAsMutable = scheme as? MUTABLE_SCHEME ?: return null
|
||||
return runCatching {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val bundledAsMutable = scheme as? MUTABLE_SCHEME ?: return@runCatching null
|
||||
return@runCatching processor.writeScheme(bundledAsMutable) as Element
|
||||
processor.writeScheme(bundledAsMutable) as Element
|
||||
}.getOrLogException(LOG)
|
||||
}
|
||||
|
||||
@@ -665,8 +655,8 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
override fun toString(): String = fileSpec
|
||||
|
||||
/**
|
||||
* Call this method before invoking [com.intellij.openapi.components.impl.stores.IComponentStore.save] to ensure that schema will be saved
|
||||
* even if there were no changes.
|
||||
* Call this method before invoking [com.intellij.openapi.components.impl.stores.IComponentStore.save]
|
||||
* to ensure that a schema will be saved even when there are no changes.
|
||||
*/
|
||||
@TestOnly
|
||||
fun forceSaving() {
|
||||
@@ -702,14 +692,7 @@ class SchemeManagerImpl<T : Scheme, MUTABLE_SCHEME : T>(
|
||||
if (error is CancellationException || error is ProcessCanceledException) {
|
||||
throw error
|
||||
}
|
||||
|
||||
val compoundError = this.error
|
||||
if (compoundError == null) {
|
||||
this.error = error
|
||||
}
|
||||
else {
|
||||
compoundError.addSuppressed(error)
|
||||
}
|
||||
this.error = addSuppressed(this.error, error)
|
||||
}
|
||||
|
||||
fun getError(): Throwable? = error
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("PropertyName")
|
||||
|
||||
package com.intellij.openapi.editor.colors.impl
|
||||
|
||||
import com.intellij.configurationStore.BundledSchemeEP
|
||||
@@ -61,7 +59,6 @@ import com.intellij.util.ui.StartupUiUtil
|
||||
import com.intellij.util.xml.dom.createXmlStreamReader
|
||||
import com.intellij.util.xmlb.annotations.OptionTag
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import java.nio.file.Path
|
||||
@@ -72,14 +69,19 @@ import javax.xml.stream.XMLStreamReader
|
||||
private val LOG: Logger
|
||||
get() = logger<EditorColorsManagerImpl>()
|
||||
|
||||
private const val TEMP_SCHEME_KEY: @NonNls String = "TEMP_SCHEME_KEY"
|
||||
private const val TEMP_SCHEME_FILE_KEY: @NonNls String = "TEMP_SCHEME_FILE_KEY"
|
||||
private const val TEMP_SCHEME_KEY: String = "TEMP_SCHEME_KEY"
|
||||
private const val TEMP_SCHEME_FILE_KEY: String = "TEMP_SCHEME_FILE_KEY"
|
||||
|
||||
@State(name = EditorColorsManagerImpl.COMPONENT_NAME, storages = [Storage(EditorColorsManagerImpl.STORAGE_NAME)],
|
||||
additionalExportDirectory = EditorColorsManagerImpl.FILE_SPEC, category = SettingsCategory.UI)
|
||||
@State(
|
||||
name = EditorColorsManagerImpl.COMPONENT_NAME,
|
||||
storages = [Storage(EditorColorsManagerImpl.STORAGE_NAME)],
|
||||
additionalExportDirectory = EditorColorsManagerImpl.FILE_SPEC,
|
||||
category = SettingsCategory.UI
|
||||
)
|
||||
@ApiStatus.Internal
|
||||
class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: SchemeManagerFactory)
|
||||
: EditorColorsManager(), PersistentStateComponent<EditorColorsManagerImpl.State?> {
|
||||
: EditorColorsManager(), PersistentStateComponent<EditorColorsManagerImpl.State?>
|
||||
{
|
||||
private val treeDispatcher = ComponentTreeEventDispatcher.create(EditorColorsListener::class.java)
|
||||
|
||||
private val schemeModificationCounter = AtomicLong()
|
||||
@@ -92,12 +94,17 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
constructor() : this(SchemeManagerFactory.getInstance())
|
||||
|
||||
init {
|
||||
val additionalTextAttributes = collectAdditionalTextAttributesEPs()
|
||||
schemeManager = schemeManagerFactory.create(directoryName = FILE_SPEC,
|
||||
processor = EditorColorSchemeProcessor(additionalTextAttributes),
|
||||
presentableName = null,
|
||||
directoryPath = null,
|
||||
settingsCategory = SettingsCategory.UI)
|
||||
val additionalTextAttributes = HashMap<String, MutableList<AdditionalTextAttributesEP>>()
|
||||
ADDITIONAL_TEXT_ATTRIBUTES_EP_NAME.forEachExtensionSafe {
|
||||
additionalTextAttributes.computeIfAbsent(it.scheme) { ArrayList() }.add(it)
|
||||
}
|
||||
schemeManager = schemeManagerFactory.create(
|
||||
directoryName = FILE_SPEC,
|
||||
EditorColorSchemeProcessor(additionalTextAttributes),
|
||||
presentableName = null,
|
||||
directoryPath = null,
|
||||
SettingsCategory.UI
|
||||
)
|
||||
for (defaultScheme in DefaultColorSchemesManager.getInstance().allSchemes) {
|
||||
schemeManager.addScheme(defaultScheme)
|
||||
}
|
||||
@@ -106,7 +113,6 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
}
|
||||
schemeManager.loadSchemes()
|
||||
loadRemainAdditionalTextAttributes(additionalTextAttributes)
|
||||
|
||||
initEditableDefaultSchemesCopies()
|
||||
initEditableBundledSchemesCopies()
|
||||
resolveLinksToBundledSchemes()
|
||||
@@ -114,17 +120,15 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
|
||||
companion object {
|
||||
@VisibleForTesting
|
||||
val ADDITIONAL_TEXT_ATTRIBUTES_EP_NAME: ExtensionPointName<AdditionalTextAttributesEP> = ExtensionPointName(
|
||||
"com.intellij.additionalTextAttributes")
|
||||
val ADDITIONAL_TEXT_ATTRIBUTES_EP_NAME: ExtensionPointName<AdditionalTextAttributesEP> =
|
||||
ExtensionPointName("com.intellij.additionalTextAttributes")
|
||||
|
||||
const val COMPONENT_NAME: String = "EditorColorsManagerImpl"
|
||||
const val STORAGE_NAME: String = "colors.scheme.xml"
|
||||
|
||||
const val FILE_SPEC: String = "colors"
|
||||
|
||||
fun isTempScheme(scheme: EditorColorsScheme?): Boolean {
|
||||
return (scheme ?: return false).getMetaProperties().getProperty(TEMP_SCHEME_KEY).toBoolean()
|
||||
}
|
||||
fun isTempScheme(scheme: EditorColorsScheme?): Boolean =
|
||||
scheme?.getMetaProperties()?.getProperty(TEMP_SCHEME_KEY).toBoolean()
|
||||
|
||||
fun getTempSchemeOriginalFilePath(scheme: EditorColorsScheme): Path? {
|
||||
if (isTempScheme(scheme)) {
|
||||
@@ -193,11 +197,9 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
createEditableCopy(initialScheme = scheme, editableCopyName = Scheme.EDITABLE_COPY_PREFIX + scheme.name, to)
|
||||
}
|
||||
}
|
||||
|
||||
for (scheme in to) {
|
||||
schemeManager.addScheme(scheme)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun resolveLinksToBundledSchemes() {
|
||||
@@ -242,7 +244,6 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
|
||||
fun schemeChangedOrSwitched(newScheme: EditorColorsScheme?) {
|
||||
dropPsiCaches()
|
||||
|
||||
callGlobalSchemeChange(newScheme)
|
||||
}
|
||||
|
||||
@@ -287,7 +288,6 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
loadAdditionalTextAttributesForScheme(scheme = scheme, attributeEps = value)
|
||||
}
|
||||
additionalTextAttributes.clear()
|
||||
@@ -295,6 +295,7 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
|
||||
class State {
|
||||
@JvmField
|
||||
@Suppress("PropertyName")
|
||||
var USE_ONLY_MONOSPACED_FONTS: Boolean = true
|
||||
|
||||
@JvmField
|
||||
@@ -316,23 +317,20 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAllSchemes(): Array<EditorColorsScheme> {
|
||||
return schemeManager.allSchemes.asSequence()
|
||||
override fun getAllSchemes(): Array<EditorColorsScheme> =
|
||||
schemeManager.allSchemes.asSequence()
|
||||
.filter { AbstractColorsScheme.isVisible(it) }
|
||||
.sortedWith(EditorColorSchemesComparator.INSTANCE)
|
||||
.toList()
|
||||
.toTypedArray()
|
||||
}
|
||||
|
||||
fun setGlobalScheme(scheme: EditorColorsScheme?, processChangeSynchronously: Boolean) {
|
||||
val notify = LoadingState.COMPONENTS_LOADED.isOccurred
|
||||
schemeManager.setCurrent(scheme = scheme ?: getDefaultScheme(),
|
||||
notify = notify,
|
||||
processChangeSynchronously = processChangeSynchronously)
|
||||
schemeManager.setCurrent(scheme ?: getDefaultScheme(), notify, processChangeSynchronously)
|
||||
}
|
||||
|
||||
override fun setGlobalScheme(scheme: EditorColorsScheme?) {
|
||||
setGlobalScheme(scheme = scheme, processChangeSynchronously = false)
|
||||
setGlobalScheme(scheme, processChangeSynchronously = false)
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
@@ -405,7 +403,7 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
return editableCopy
|
||||
}
|
||||
|
||||
override fun getScheme(schemeName: @NonNls String): EditorColorsScheme? {
|
||||
override fun getScheme(schemeName: String): EditorColorsScheme? {
|
||||
if (schemeName.endsWith(".xml")) {
|
||||
val path = schemeName.removeSuffix(".xml").removePrefix("/")
|
||||
return schemeManager.allSchemes.firstOrNull {
|
||||
@@ -420,8 +418,11 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
if (isTempScheme(scheme)) {
|
||||
return scheme
|
||||
}
|
||||
|
||||
val editableCopyName = getEditableCopyName(scheme)
|
||||
val editableCopyName = when {
|
||||
scheme is DefaultColorsScheme && scheme.hasEditableCopy() -> scheme.editableCopyName
|
||||
scheme is BundledEditorColorScheme -> Scheme.EDITABLE_COPY_PREFIX + scheme.name
|
||||
else -> null
|
||||
}
|
||||
if (editableCopyName != null) {
|
||||
getScheme(editableCopyName)?.let {
|
||||
return it
|
||||
@@ -438,9 +439,7 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
return state
|
||||
}
|
||||
|
||||
override fun isUseOnlyMonospacedFonts(): Boolean {
|
||||
return state.USE_ONLY_MONOSPACED_FONTS
|
||||
}
|
||||
override fun isUseOnlyMonospacedFonts(): Boolean = state.USE_ONLY_MONOSPACED_FONTS
|
||||
|
||||
override fun setUseOnlyMonospacedFonts(value: Boolean) {
|
||||
state.USE_ONLY_MONOSPACED_FONTS = value
|
||||
@@ -455,7 +454,6 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
if (colorSchemeName != null) {
|
||||
LOG.warn("$colorSchemeName color scheme is missing")
|
||||
}
|
||||
|
||||
noStateLoaded()
|
||||
return
|
||||
}
|
||||
@@ -474,6 +472,7 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
schemeManager.setCurrent(scheme = colorScheme, notify = isInitialConfigurationLoaded)
|
||||
isInitialConfigurationLoaded = true
|
||||
|
||||
@Suppress("UNNECESSARY_SAFE_CALL")
|
||||
colorScheme?.let {
|
||||
notifyAboutSolarizedColorSchemeDeprecationIfSet(scheme = it)
|
||||
}
|
||||
@@ -547,7 +546,7 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
scheme.readExternal(dataHolder.read())
|
||||
// We don't need to update digest for a bundled scheme because:
|
||||
// 1) it can be computed on demand later (because a bundled scheme is not mutable)
|
||||
// 2) in the future user copy of a bundled scheme will use a bundled scheme as parent (not as full copy)
|
||||
// 2) in the future, user copies of bundled schemes will use a bundled scheme as parent (not as full copy)
|
||||
if (isBundled ||
|
||||
(ApplicationManager.getApplication().isUnitTestMode() && scheme.metaProperties.getProperty("forceOptimize").toBoolean())) {
|
||||
if (scheme.parentScheme is AbstractColorsScheme) {
|
||||
@@ -562,9 +561,8 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
return scheme
|
||||
}
|
||||
|
||||
override fun getState(scheme: EditorColorsScheme): SchemeState {
|
||||
return if (scheme.isReadOnly) SchemeState.NON_PERSISTENT else SchemeState.POSSIBLY_CHANGED
|
||||
}
|
||||
override fun getState(scheme: EditorColorsScheme): SchemeState =
|
||||
if (scheme.isReadOnly) SchemeState.NON_PERSISTENT else SchemeState.POSSIBLY_CHANGED
|
||||
|
||||
override fun onCurrentSchemeSwitched(
|
||||
oldScheme: EditorColorsScheme?,
|
||||
@@ -594,16 +592,15 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
schemeChangedOrSwitched(newScheme)
|
||||
}
|
||||
|
||||
override val schemeExtension: @NonNls String
|
||||
override val schemeExtension: String
|
||||
get() = getColorSchemeFileExtension()
|
||||
|
||||
override fun isSchemeEqualToBundled(scheme: EditorColorsSchemeImpl): Boolean {
|
||||
if (!scheme.getName().startsWith(Scheme.EDITABLE_COPY_PREFIX)) {
|
||||
if (!scheme.name.startsWith(Scheme.EDITABLE_COPY_PREFIX)) {
|
||||
return false
|
||||
}
|
||||
|
||||
val bundledScheme =
|
||||
(schemeManager.findSchemeByName(scheme.getName().substring(Scheme.EDITABLE_COPY_PREFIX.length)) as AbstractColorsScheme?)
|
||||
(schemeManager.findSchemeByName(scheme.name.substring(Scheme.EDITABLE_COPY_PREFIX.length)) as AbstractColorsScheme?)
|
||||
?: return false
|
||||
return scheme.settingsEqual(bundledScheme)
|
||||
}
|
||||
@@ -618,17 +615,111 @@ class EditorColorsManagerImpl @NonInjectable constructor(schemeManagerFactory: S
|
||||
ApplicationManager.getApplication().getMessageBus().syncPublisher(EditorColorsManagerListener.TOPIC).schemesReloaded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val isHeadlessMode: Boolean
|
||||
get() = ApplicationManager.getApplication().isHeadlessEnvironment()
|
||||
private val isHeadlessMode: Boolean
|
||||
get() = ApplicationManager.getApplication().isHeadlessEnvironment()
|
||||
|
||||
private fun collectAdditionalTextAttributesEPs(): MutableMap<String, MutableList<AdditionalTextAttributesEP>> {
|
||||
val result = HashMap<String, MutableList<AdditionalTextAttributesEP>>()
|
||||
EditorColorsManagerImpl.ADDITIONAL_TEXT_ATTRIBUTES_EP_NAME.forEachExtensionSafe {
|
||||
result.computeIfAbsent(it.scheme) { ArrayList() }.add(it)
|
||||
private fun notifyAboutSolarizedColorSchemeDeprecationIfSet(scheme: EditorColorsScheme) {
|
||||
val solarizedColorSchemeNames = setOf(
|
||||
"Solarized (dark)", "Solarized (light)", "Solarized Dark", "Solarized Light", "Solarized Dark (Darcula)"
|
||||
)
|
||||
|
||||
val name = scheme.getName().removePrefix(Scheme.EDITABLE_COPY_PREFIX)
|
||||
if (!solarizedColorSchemeNames.contains(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (name == "Solarized Dark" || name == "Solarized Light") {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
val solarizedPluginsContainingSchemesWithTheSameName = arrayOf(
|
||||
PluginId.getId("solarized"),
|
||||
PluginId.getId("com.tylerthrailkill.intellij.solarized")
|
||||
)
|
||||
for (t in solarizedPluginsContainingSchemesWithTheSameName) {
|
||||
if (PluginManager.getInstance().findEnabledPlugin(t) != null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val connection = ApplicationManager.getApplication().getMessageBus().connect()
|
||||
connection.subscribe<ProjectManagerListener>(ProjectManager.TOPIC, object : ProjectManagerListener {
|
||||
@Suppress("removal", "OVERRIDE_DEPRECATION")
|
||||
override fun projectOpened(project: Project) {
|
||||
connection.disconnect()
|
||||
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
{
|
||||
val pluginId = PluginId.getId("com.4lex4.intellij.solarized")
|
||||
val isDark = ColorUtil.isDark(scheme.getDefaultBackground())
|
||||
val neededThemeName = if (isDark) "Solarized Dark" else "Solarized Light"
|
||||
val neededTheme = UiThemeProviderListManager.getInstance().findThemeByName(neededThemeName)
|
||||
val notification = Notification("ColorSchemeDeprecation", IdeBundle.message(
|
||||
"notification.title.solarized.color.scheme.deprecation"), NotificationType.ERROR)
|
||||
if (neededTheme != null) {
|
||||
notification.setContent(IdeBundle.message(
|
||||
"notification.content.solarized.color.scheme.deprecation.enable", name,
|
||||
neededThemeName))
|
||||
notification.addAction(object : NotificationAction(IdeBundle.message(
|
||||
"notification.title.enable.action.solarized.color.scheme.deprecation",
|
||||
neededThemeName)) {
|
||||
override fun actionPerformed(e: AnActionEvent, notification: Notification) {
|
||||
val lafManager = LafManager.getInstance()
|
||||
lafManager.setCurrentLookAndFeel(neededTheme, false)
|
||||
lafManager.updateUI()
|
||||
notification.expire()
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
notification.setContent(IdeBundle.message(
|
||||
"notification.content.solarized.color.scheme.deprecation.install", name,
|
||||
"Solarized Themes"))
|
||||
notification.addAction(object : NotificationAction(IdeBundle.message(
|
||||
"notification.title.install.action.solarized.color.scheme.deprecation")) {
|
||||
override fun actionPerformed(e: AnActionEvent, notification: Notification) {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val connection = ApplicationManager.getApplication().getMessageBus().connect()
|
||||
// Needed to enable matching theme after plugin installation.
|
||||
// Since the plugin provides two themes, we need to wait for both of them to be added
|
||||
// (and applied) to reapply the needed one if it wasn't added last.
|
||||
connection.subscribe(LafManagerListener.TOPIC, object : LafManagerListener {
|
||||
private var matchingTheme: UIThemeLookAndFeelInfo? = null
|
||||
private var otherWasSet: Boolean = false
|
||||
|
||||
override fun lookAndFeelChanged(source: LafManager) {
|
||||
val themeInfo = source.getCurrentUIThemeLookAndFeel()
|
||||
if (themeInfo.name.contains("Solarized")) {
|
||||
if ((isDark && themeInfo.isDark) || (!isDark && !themeInfo.isDark)) {
|
||||
matchingTheme = themeInfo
|
||||
}
|
||||
else {
|
||||
otherWasSet = true
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingTheme != null && otherWasSet) {
|
||||
connection.disconnect()
|
||||
|
||||
if (source.getCurrentUIThemeLookAndFeel() != matchingTheme) {
|
||||
source.setCurrentLookAndFeel(matchingTheme!!, false)
|
||||
source.updateUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
installAndEnable(project = project, pluginIds = setOf(pluginId), onSuccess = notification::expire)
|
||||
}
|
||||
})
|
||||
}
|
||||
notification.notify(project)
|
||||
},
|
||||
ModalityState.nonModal(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun loadAdditionalTextAttributesForScheme(scheme: AbstractColorsScheme, attributeEps: Collection<AdditionalTextAttributesEP>) {
|
||||
@@ -652,122 +743,6 @@ private fun loadAdditionalTextAttributesForScheme(scheme: AbstractColorsScheme,
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEditableCopyName(scheme: EditorColorsScheme?): String? {
|
||||
return when {
|
||||
scheme is DefaultColorsScheme && scheme.hasEditableCopy() -> scheme.editableCopyName
|
||||
scheme is BundledEditorColorScheme -> Scheme.EDITABLE_COPY_PREFIX + scheme.getName()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyAboutSolarizedColorSchemeDeprecationIfSet(scheme: EditorColorsScheme) {
|
||||
val solarizedColorSchemeNames = setOf("Solarized (dark)",
|
||||
"Solarized (light)",
|
||||
"Solarized Dark",
|
||||
"Solarized Light",
|
||||
"Solarized Dark (Darcula)")
|
||||
|
||||
val name = scheme.getName().removePrefix(Scheme.EDITABLE_COPY_PREFIX)
|
||||
if (!solarizedColorSchemeNames.contains(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (name == "Solarized Dark" || name == "Solarized Light") {
|
||||
@Suppress("SpellCheckingInspection")
|
||||
val solarizedPluginsContainingSchemesWithTheSameName = arrayOf(
|
||||
PluginId.getId("solarized"),
|
||||
PluginId.getId("com.tylerthrailkill.intellij.solarized")
|
||||
)
|
||||
for (t in solarizedPluginsContainingSchemesWithTheSameName) {
|
||||
if (PluginManager.getInstance().findEnabledPlugin(t) != null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val connection = ApplicationManager.getApplication().getMessageBus().connect()
|
||||
connection.subscribe<ProjectManagerListener>(ProjectManager.TOPIC, object : ProjectManagerListener {
|
||||
@Suppress("removal", "OVERRIDE_DEPRECATION")
|
||||
override fun projectOpened(project: Project) {
|
||||
connection.disconnect()
|
||||
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
{
|
||||
val pluginId = PluginId.getId("com.4lex4.intellij.solarized")
|
||||
val isDark = ColorUtil.isDark(scheme.getDefaultBackground())
|
||||
val neededThemeName = if (isDark) "Solarized Dark" else "Solarized Light"
|
||||
|
||||
val neededTheme = UiThemeProviderListManager.getInstance().findThemeByName(neededThemeName)
|
||||
val notification = Notification("ColorSchemeDeprecation",
|
||||
IdeBundle.message(
|
||||
"notification.title.solarized.color.scheme.deprecation"),
|
||||
"",
|
||||
NotificationType.ERROR)
|
||||
if (neededTheme != null) {
|
||||
notification.setContent(IdeBundle.message(
|
||||
"notification.content.solarized.color.scheme.deprecation.enable", name,
|
||||
neededThemeName))
|
||||
notification.addAction(object : NotificationAction(IdeBundle.message(
|
||||
"notification.title.enable.action.solarized.color.scheme.deprecation",
|
||||
neededThemeName)) {
|
||||
override fun actionPerformed(e: AnActionEvent, notification: Notification) {
|
||||
val lafManager = LafManager.getInstance()
|
||||
lafManager.setCurrentLookAndFeel(neededTheme, false)
|
||||
lafManager.updateUI()
|
||||
notification.expire()
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
notification.setContent(IdeBundle.message(
|
||||
"notification.content.solarized.color.scheme.deprecation.install", name,
|
||||
"Solarized Themes"))
|
||||
notification.addAction(object : NotificationAction(IdeBundle.message(
|
||||
"notification.title.install.action.solarized.color.scheme.deprecation")) {
|
||||
override fun actionPerformed(e: AnActionEvent, notification: Notification) {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val connection = ApplicationManager.getApplication().getMessageBus().connect()
|
||||
// Needed to enable matching theme after plugin installation.
|
||||
// Since the plugin provides two themes, we need to wait for both of them to be added
|
||||
// (and applied) to reapply the needed one if it wasn't added last.
|
||||
connection.subscribe(LafManagerListener.TOPIC, object : LafManagerListener {
|
||||
private var matchingTheme: UIThemeLookAndFeelInfo? = null
|
||||
private var otherWasSet: Boolean = false
|
||||
|
||||
override fun lookAndFeelChanged(source: LafManager) {
|
||||
val themeInfo = source.getCurrentUIThemeLookAndFeel()
|
||||
if (themeInfo.name.contains("Solarized")) {
|
||||
if ((isDark && themeInfo.isDark) || (!isDark && !themeInfo.isDark)) {
|
||||
matchingTheme = themeInfo
|
||||
}
|
||||
else {
|
||||
otherWasSet = true
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingTheme != null && otherWasSet) {
|
||||
connection.disconnect()
|
||||
|
||||
if (source.getCurrentUIThemeLookAndFeel() != matchingTheme) {
|
||||
source.setCurrentLookAndFeel(matchingTheme!!, false)
|
||||
source.updateUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
installAndEnable(project = project, pluginIds = setOf(pluginId), onSuccess = notification::expire)
|
||||
}
|
||||
})
|
||||
}
|
||||
notification.notify(project)
|
||||
},
|
||||
ModalityState.nonModal(),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun readEditorSchemeNameFromXml(parser: XMLStreamReader): String? {
|
||||
var eventType = parser.eventType
|
||||
@@ -787,8 +762,7 @@ private val BUNDLED_EP_NAME = ExtensionPointName<BundledSchemeEP>("com.intellij.
|
||||
fun createLoadBundledSchemeRequests(
|
||||
additionalTextAttributes: MutableMap<String, MutableList<AdditionalTextAttributesEP>>,
|
||||
checkId: Boolean = false,
|
||||
)
|
||||
: Sequence<SchemeManager.LoadBundleSchemeRequest<EditorColorsScheme>> {
|
||||
) : Sequence<SchemeManager.LoadBundleSchemeRequest<EditorColorsScheme>> {
|
||||
return sequence {
|
||||
for (item in BUNDLED_EP_NAME.filterableLazySequence()) {
|
||||
val pluginDescriptor = item.pluginDescriptor
|
||||
@@ -796,8 +770,8 @@ fun createLoadBundledSchemeRequests(
|
||||
val resourcePath = (bean.path ?: continue).removePrefix("/").let { if (it.endsWith(".xml")) it else "$it.xml" }
|
||||
|
||||
yield(object : SchemeManager.LoadBundleSchemeRequest<EditorColorsScheme> {
|
||||
override val pluginId: PluginId
|
||||
get() = pluginDescriptor.pluginId
|
||||
override val pluginId: PluginId = pluginDescriptor.pluginId
|
||||
|
||||
override val schemeKey: String
|
||||
get() {
|
||||
val idFromExtension = item.id
|
||||
@@ -821,13 +795,11 @@ fun createLoadBundledSchemeRequests(
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadBytes(): ByteArray {
|
||||
return ResourceUtil.getResourceAsBytes(resourcePath, pluginDescriptor.classLoader)!!
|
||||
}
|
||||
override fun loadBytes(): ByteArray =
|
||||
ResourceUtil.getResourceAsBytes(resourcePath, pluginDescriptor.classLoader)!!
|
||||
|
||||
override fun createScheme(): EditorColorsScheme {
|
||||
return createBundledEditorColorScheme(resourcePath, additionalTextAttributes, loadBytes(), pluginId)
|
||||
}
|
||||
override fun createScheme(): EditorColorsScheme =
|
||||
createBundledEditorColorScheme(resourcePath, additionalTextAttributes, loadBytes(), pluginId)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -839,7 +811,7 @@ fun createLoadBundledSchemeRequests(
|
||||
|
||||
val uiTheme = item.theme.get() ?: continue
|
||||
val editorSchemeId = uiTheme.theme.originalEditorSchemeId ?: continue
|
||||
// we must check originalEditorSchemeId to load its corresponding editor scheme on `reloadKeepingActiveScheme` call
|
||||
// we must check `originalEditorSchemeId` to load its corresponding editor scheme on the `reloadKeepingActiveScheme` call
|
||||
if (!editorSchemeId.endsWith(".xml")) {
|
||||
continue
|
||||
}
|
||||
@@ -861,20 +833,15 @@ fun createLoadBundledSchemeRequests(
|
||||
reader.close()
|
||||
}
|
||||
|
||||
// update id to make sure that other consumers of id uses a correct one
|
||||
// update ID to make sure that other consumers use a correct one
|
||||
uiTheme.theme.editorSchemeId = colorSchemeId
|
||||
|
||||
yield(object : SchemeManager.LoadBundleSchemeRequest<EditorColorsScheme> {
|
||||
override val pluginId: PluginId
|
||||
get() = pluginDescriptor.pluginId
|
||||
override val schemeKey: String
|
||||
get() = colorSchemeId
|
||||
|
||||
override val pluginId: PluginId = pluginDescriptor.pluginId
|
||||
override val schemeKey: String = colorSchemeId
|
||||
override fun loadBytes(): ByteArray = data
|
||||
|
||||
override fun createScheme(): EditorColorsScheme {
|
||||
return createBundledEditorColorScheme(resourcePath, additionalTextAttributes, loadBytes(), pluginId)
|
||||
}
|
||||
override fun createScheme(): EditorColorsScheme =
|
||||
createBundledEditorColorScheme(resourcePath, additionalTextAttributes, loadBytes(), pluginId)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -891,11 +858,11 @@ private fun createBundledEditorColorScheme(
|
||||
scheme.readExternal(JDOMUtil.load(data))
|
||||
// We don't need to update digest for a bundled scheme because:
|
||||
// 1) it can be computed on demand later (because a bundled scheme is not mutable)
|
||||
// 2) in the future user copy of a bundled scheme will use a bundled scheme as parent (not as full copy)
|
||||
// 2) in the future, user copies of bundled schemes will use a bundled scheme as parent (not as full copy)
|
||||
if (scheme.parentScheme is AbstractColorsScheme) {
|
||||
val attributesEPs = additionalTextAttributes.remove(scheme.parentScheme.getName())
|
||||
if (!attributesEPs.isNullOrEmpty()) {
|
||||
loadAdditionalTextAttributesForScheme(scheme = scheme.parentScheme as AbstractColorsScheme, attributeEps = attributesEPs)
|
||||
loadAdditionalTextAttributesForScheme(scheme.parentScheme as AbstractColorsScheme, attributesEPs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,12 +871,9 @@ private fun createBundledEditorColorScheme(
|
||||
return scheme
|
||||
}
|
||||
|
||||
internal class BundledEditorColorScheme(@JvmField val resourcePath: String) : EditorColorsSchemeImpl(/* parentScheme = */ null) {
|
||||
private class BundledEditorColorScheme(@JvmField val resourcePath: String) : EditorColorsSchemeImpl(/* parentScheme = */ null) {
|
||||
override fun isVisible() = false
|
||||
|
||||
override fun isReadOnly() = true
|
||||
|
||||
override fun getSchemeState() = SchemeState.UNCHANGED
|
||||
|
||||
override fun isFromIntellij(): Boolean = (metaProperties.get("pluginId") as? String)?.startsWith("com.intellij") == true
|
||||
}
|
||||
override fun isFromIntellij(): Boolean = (metaProperties["pluginId"] as? String)?.startsWith("com.intellij") == true
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import com.intellij.openapi.progress.blockingContext
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
|
||||
/**
|
||||
* Service, which implements this interfaces, will be asked to [save] custom settings (in their own custom way)
|
||||
* when application (for Application level services) or project (for Project level services) is invoked.
|
||||
* Services which implement this interface will be asked to [save] custom settings (in their own custom way)
|
||||
* when application (for application level services) or project (for project level services) settings save is called.
|
||||
*/
|
||||
@Internal
|
||||
interface SettingsSavingComponent {
|
||||
@@ -21,4 +21,4 @@ interface SettingsSavingComponentJavaAdapter : SettingsSavingComponent {
|
||||
}
|
||||
|
||||
fun doSave()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// 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
|
||||
|
||||
import com.intellij.openapi.components.SettingsCategory
|
||||
@@ -17,7 +17,7 @@ abstract class SchemeManager<T> {
|
||||
abstract val activeScheme: T?
|
||||
|
||||
/**
|
||||
* If the schemes are lazily loaded, you can utilize this method to delay scheme selection.
|
||||
* If schemes are lazily loaded, you can use this method to delay scheme selection.
|
||||
* The scheme will then be located by its name upon the first use.
|
||||
*/
|
||||
abstract var currentSchemeName: String?
|
||||
@@ -35,7 +35,7 @@ abstract class SchemeManager<T> {
|
||||
abstract fun reload(retainFilter: ((scheme: T) -> Boolean)?)
|
||||
|
||||
fun addScheme(scheme: T) {
|
||||
addScheme(scheme, true)
|
||||
addScheme(scheme, replaceExisting = true)
|
||||
}
|
||||
|
||||
abstract fun addScheme(scheme: T, replaceExisting: Boolean)
|
||||
@@ -45,17 +45,15 @@ abstract class SchemeManager<T> {
|
||||
abstract fun setCurrentSchemeName(schemeName: String?, notify: Boolean)
|
||||
|
||||
@JvmOverloads
|
||||
open fun setCurrent(scheme: T?, notify: Boolean = true, processChangeSynchronously: Boolean = false) {
|
||||
}
|
||||
open fun setCurrent(scheme: T?, notify: Boolean = true, processChangeSynchronously: Boolean = false) { }
|
||||
|
||||
abstract fun removeScheme(scheme: T): Boolean
|
||||
|
||||
abstract fun removeScheme(name: String): T?
|
||||
|
||||
/**
|
||||
* Must be called before [.loadSchemes].
|
||||
*
|
||||
* Scheme manager processor must be LazySchemeProcessor
|
||||
* Must be called before [loadSchemes].
|
||||
* Scheme manager processor must be [com.intellij.configurationStore.LazySchemeProcessor].
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
abstract fun loadBundledScheme(resourceName: String, requestor: Any?, pluginDescriptor: PluginDescriptor?): T?
|
||||
@@ -63,11 +61,8 @@ abstract class SchemeManager<T> {
|
||||
@ApiStatus.Internal
|
||||
interface LoadBundleSchemeRequest<T> {
|
||||
val pluginId: PluginId
|
||||
|
||||
val schemeKey: String
|
||||
|
||||
fun loadBytes(): ByteArray
|
||||
|
||||
fun createScheme(): T
|
||||
}
|
||||
|
||||
@@ -75,8 +70,7 @@ abstract class SchemeManager<T> {
|
||||
abstract fun loadBundledSchemes(providers: Sequence<LoadBundleSchemeRequest<T>>)
|
||||
|
||||
@JvmOverloads
|
||||
open fun setSchemes(newSchemes: List<T>, newCurrentScheme: T? = null, removeCondition: Predicate<T>? = null) {
|
||||
}
|
||||
open fun setSchemes(newSchemes: List<T>, newCurrentScheme: T? = null, removeCondition: Predicate<T>? = null) { }
|
||||
|
||||
/**
|
||||
* Bundled / read-only (or overriding) scheme cannot be renamed or deleted.
|
||||
|
||||
Reference in New Issue
Block a user