mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
configuration file: track file changes, continue RunConfigurationTemplateProvider
This commit is contained in:
@@ -34,7 +34,7 @@ private val ORDER = arrayOf(CONFIGURATION_TYPE, //Application
|
||||
)
|
||||
|
||||
@RunsInEdt
|
||||
class RunConfigurableTest {
|
||||
internal class RunConfigurableTest {
|
||||
companion object {
|
||||
@JvmField
|
||||
@ClassRule
|
||||
|
||||
@@ -215,6 +215,10 @@ public abstract class RunConfigurationBase extends UserDataHolderBase implements
|
||||
myOptions = XmlSerializer.deserialize(element, getOptionsClass());
|
||||
}
|
||||
|
||||
public final void setState(@NotNull BaseState state) {
|
||||
myOptions = (RunConfigurationOptions)state;
|
||||
}
|
||||
|
||||
// we can break compatibility and make this method final (API is new and used only by our plugins), but let's avoid any inconvenience and mark as "final" after/prior to 2018.3 release.
|
||||
/**
|
||||
* Do not override this method, use {@link ConfigurationFactory#getOptionsClass()}.
|
||||
|
||||
+3
-4
@@ -4,7 +4,6 @@ package com.intellij.execution.configurations;
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.Executor;
|
||||
import com.intellij.execution.runners.ExecutionEnvironment;
|
||||
import com.intellij.openapi.options.ConfigurationException;
|
||||
import com.intellij.openapi.options.SettingsEditor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
@@ -21,7 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
/**
|
||||
* @author spleaner
|
||||
*/
|
||||
public class UnknownRunConfiguration implements RunConfiguration, WithoutOwnBeforeRunSteps {
|
||||
public final class UnknownRunConfiguration implements RunConfiguration, WithoutOwnBeforeRunSteps {
|
||||
private final ConfigurationFactory myFactory;
|
||||
private Element myStoredElement;
|
||||
private String myName;
|
||||
@@ -121,7 +120,7 @@ public class UnknownRunConfiguration implements RunConfiguration, WithoutOwnBefo
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnknownSettingsEditor extends SettingsEditor<UnknownRunConfiguration> {
|
||||
private static final class UnknownSettingsEditor extends SettingsEditor<UnknownRunConfiguration> {
|
||||
private final JPanel myPanel;
|
||||
|
||||
private UnknownSettingsEditor() {
|
||||
@@ -136,7 +135,7 @@ public class UnknownRunConfiguration implements RunConfiguration, WithoutOwnBefo
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyEditorTo(@NotNull final UnknownRunConfiguration s) throws ConfigurationException {
|
||||
protected void applyEditorTo(@NotNull final UnknownRunConfiguration s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -640,7 +640,7 @@ open class RunManagerImpl(val project: Project) : RunManagerEx(), PersistentStat
|
||||
|
||||
private fun runConfigurationFirstLoaded() {
|
||||
if (selectedConfiguration == null) {
|
||||
selectedConfiguration = allSettings.firstOrNull { it.type !is UnknownRunConfiguration }
|
||||
selectedConfiguration = allSettings.firstOrNull { it.type !== UnknownConfigurationType.getInstance() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,8 +730,15 @@ open class RunManagerImpl(val project: Project) : RunManagerEx(), PersistentStat
|
||||
internal fun addConfiguration(element: Element, settings: RunnerAndConfigurationSettingsImpl, isCheckRecentsLimit: Boolean = true) {
|
||||
if (settings.isTemplate) {
|
||||
val factory = settings.factory
|
||||
lock.write {
|
||||
templateIdToConfiguration.put(getFactoryKey(factory), settings)
|
||||
// do not register unknown RC type templates (it is saved in any case in the scheme manager, so, not lost on save)
|
||||
if (factory !== UnknownConfigurationType.getInstance()) {
|
||||
val key = getFactoryKey(factory)
|
||||
lock.write {
|
||||
val old = templateIdToConfiguration.put(key, settings)
|
||||
if (old != null) {
|
||||
LOG.error("Template $key already registered, old: $old, new: $settings")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
+5
-9
@@ -192,15 +192,11 @@ class RunnerAndConfigurationSettingsImpl @JvmOverloads constructor(val manager:
|
||||
}
|
||||
}
|
||||
|
||||
val configuration = when {
|
||||
isTemplate -> manager.getConfigurationTemplate(factory).configuration
|
||||
else -> {
|
||||
// shouldn't call createConfiguration since it calls StepBeforeRunProviders that
|
||||
// may not be loaded yet. This creates initialization order issue.
|
||||
val configuration = factory.createTemplateConfiguration(manager.project, manager)
|
||||
configuration.name = element.getAttributeValue(NAME_ATTR) ?: return
|
||||
configuration
|
||||
}
|
||||
val configuration = factory.createTemplateConfiguration(manager.project, manager)
|
||||
if (!isTemplate) {
|
||||
// shouldn't call createConfiguration since it calls StepBeforeRunProviders that
|
||||
// may not be loaded yet. This creates initialization order issue.
|
||||
configuration.name = element.getAttributeValue(NAME_ATTR) ?: return
|
||||
}
|
||||
|
||||
_configuration = configuration
|
||||
|
||||
@@ -35,6 +35,9 @@ public interface RunManagerListener extends EventListener {
|
||||
default void endUpdate() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called also in case when RunManager doesn't have state.
|
||||
*/
|
||||
default void stateLoaded(@NotNull RunManager runManager, boolean isFirstLoadState) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
<ProviderFactory implementation="com.intellij.configurationScript.IntellijConfigurationJsonSchemaProviderFactory"/>
|
||||
</extensions>
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<applicationInitializedListener implementation="com.intellij.configurationScript.IntellijConfigurationAppInitializer"/>
|
||||
<projectService serviceImplementation="com.intellij.configurationScript.ConfigurationFileManager"/>
|
||||
|
||||
<runConfigurationTemplateProvider implementation="com.intellij.configurationScript.MyRunConfigurationTemplateProvider"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.intellij.configurationScript
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.ClearableLazyValue
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.openapi.vfs.newvfs.BulkFileListener
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
|
||||
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
|
||||
internal const val IDE_FILE = "intellij.yaml"
|
||||
internal const val IDE_FILE_VARIANT_2 = "intellij.yml"
|
||||
|
||||
internal class ConfigurationFileManager(project: Project) {
|
||||
private val clearableLazyValues = ContainerUtil.createConcurrentList<ClearableLazyValue<*>>()
|
||||
|
||||
fun registerClearableLazyValue(value: ClearableLazyValue<*>) {
|
||||
clearableLazyValues.add(value)
|
||||
}
|
||||
|
||||
init {
|
||||
project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
|
||||
override fun after(events: List<VFileEvent>) {
|
||||
for (event in events) {
|
||||
if (event is VFileCopyEvent) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (event is VFileCreateEvent) {
|
||||
// VFileCreateEvent computes file on request, so, avoid getFile call
|
||||
if (event.isDirectory || !(event.childName == IDE_FILE || event.childName == IDE_FILE_VARIANT_2)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
else {
|
||||
val file = event.file ?: continue
|
||||
if (!isConfigurationFile(file)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
clearableLazyValues.forEach { it.drop() }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// todo check parent?
|
||||
internal fun isConfigurationFile(file: VirtualFile): Boolean {
|
||||
val nameSequence = file.nameSequence
|
||||
return StringUtil.equals(nameSequence, IDE_FILE) || StringUtil.equals(nameSequence, IDE_FILE_VARIANT_2)
|
||||
}
|
||||
+34
-44
@@ -1,15 +1,14 @@
|
||||
package com.intellij.configurationScript
|
||||
|
||||
import com.intellij.execution.RunManager
|
||||
import com.intellij.execution.RunManagerListener
|
||||
import com.intellij.execution.configurations.ConfigurationFactory
|
||||
import com.intellij.execution.impl.RUN_CONFIGURATION_TEMPLATE_PROVIDER_EP
|
||||
import com.intellij.execution.configurations.RunConfigurationBase
|
||||
import com.intellij.execution.impl.RunConfigurationTemplateProvider
|
||||
import com.intellij.execution.impl.RunManagerImpl
|
||||
import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl
|
||||
import com.intellij.ide.ApplicationInitializedListener
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.BaseState
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.AtomicClearableLazyValue
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.util.io.exists
|
||||
import com.intellij.util.io.inputStreamIfExists
|
||||
@@ -22,61 +21,55 @@ import java.io.Reader
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
internal class IntellijConfigurationAppInitializer : ApplicationInitializedListener {
|
||||
override fun componentsInitialized() {
|
||||
ApplicationManager.getApplication().messageBus.connect().subscribe(RunManagerListener.TOPIC, object: RunManagerListener {
|
||||
override fun stateLoaded(runManager: RunManager, isFirstLoadState: Boolean) {
|
||||
if (!isFirstLoadState || !Registry.`is`("run.manager.use.intellij.config.file", false)) {
|
||||
return
|
||||
}
|
||||
|
||||
val project = (runManager as? RunManagerImpl)?.project ?: return
|
||||
// todo listen file changes
|
||||
val file = findConfigurationFile(project) ?: return
|
||||
val inputStream = file.inputStreamIfExists() ?: return
|
||||
val map = THashMap<ConfigurationFactory, FactoryEntry>()
|
||||
inputStream.use {
|
||||
parseConfigurationFile(it.bufferedReader()) { factory, state ->
|
||||
map.put(factory, FactoryEntry(state))
|
||||
}
|
||||
}
|
||||
RUN_CONFIGURATION_TEMPLATE_PROVIDER_EP.findExtension(MyRunConfigurationTemplateProvider::class.java, project)!!.setTemplates(map)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private class FactoryEntry(state: Any) {
|
||||
var state: Any? = state
|
||||
var settings: RunnerAndConfigurationSettingsImpl? = null
|
||||
}
|
||||
|
||||
private class MyRunConfigurationTemplateProvider : RunConfigurationTemplateProvider {
|
||||
@Volatile
|
||||
private var map: Map<ConfigurationFactory, FactoryEntry>? = null
|
||||
private class MyRunConfigurationTemplateProvider(private val project: Project) : RunConfigurationTemplateProvider {
|
||||
private val map = object : AtomicClearableLazyValue<Map<ConfigurationFactory, FactoryEntry>>() {
|
||||
override fun compute(): Map<ConfigurationFactory, FactoryEntry> {
|
||||
val file = findConfigurationFile(project) ?: return emptyMap()
|
||||
val inputStream = file.inputStreamIfExists() ?: return emptyMap()
|
||||
val map = THashMap<ConfigurationFactory, FactoryEntry>()
|
||||
inputStream.use {
|
||||
parseConfigurationFile(it.bufferedReader(), isTemplatesOnly = true) { factory, state ->
|
||||
map.put(factory, FactoryEntry(state))
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
project.service<ConfigurationFileManager>().registerClearableLazyValue(map)
|
||||
}
|
||||
|
||||
override fun getRunConfigurationTemplate(factory: ConfigurationFactory, runManager: RunManagerImpl): RunnerAndConfigurationSettingsImpl? {
|
||||
val item = map?.get(factory) ?: return null
|
||||
if (!Registry.`is`("run.manager.use.intellij.config.file", false)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val item = map.value.get(factory) ?: return null
|
||||
synchronized(item) {
|
||||
var settings = item.settings
|
||||
if (settings != null) {
|
||||
return settings
|
||||
}
|
||||
|
||||
settings = RunnerAndConfigurationSettingsImpl(runManager, isTemplate = true)
|
||||
val configuration = factory.createTemplateConfiguration(runManager.project, runManager)
|
||||
(configuration as RunConfigurationBase).setState(item.state as BaseState)
|
||||
settings = RunnerAndConfigurationSettingsImpl(runManager, configuration, isTemplate = true, isSingleton = factory.singletonPolicy.isSingleton)
|
||||
item.state = null
|
||||
item.settings = settings
|
||||
return settings
|
||||
}
|
||||
}
|
||||
|
||||
internal fun setTemplates(value: Map<ConfigurationFactory, FactoryEntry>) {
|
||||
map = value
|
||||
}
|
||||
}
|
||||
|
||||
// we cannot use the same approach as we generate JSON scheme because we should load option classes only in a lazy manner
|
||||
// that's why we don't use snakeyaml TypeDescription approach to load
|
||||
internal fun parseConfigurationFile(reader: Reader, processor: (factory: ConfigurationFactory, state: Any) -> Unit) {
|
||||
internal fun parseConfigurationFile(reader: Reader, isTemplatesOnly: Boolean, processor: (factory: ConfigurationFactory, state: Any) -> Unit) {
|
||||
val yaml = Yaml(SafeConstructor())
|
||||
// later we can avoid full node graph building, but for now just use simple implementation (problem is that Yaml supports references and merge - proper support of it can be tricky)
|
||||
// "load" under the hood uses "compose" - i.e. Yaml itself doesn't use stream API to build object model.
|
||||
@@ -86,7 +79,7 @@ internal fun parseConfigurationFile(reader: Reader, processor: (factory: Configu
|
||||
val keyNode = tuple.keyNode
|
||||
if (keyNode is ScalarNode && keyNode.value == Keys.runConfigurations) {
|
||||
val rcTypeGroupNode = tuple.valueNode as? MappingNode ?: continue
|
||||
dataReader.read(rcTypeGroupNode)
|
||||
dataReader.read(rcTypeGroupNode, isTemplatesOnly)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +95,4 @@ private fun findConfigurationFile(project: Project): Path? {
|
||||
file = projectIdeaDir.resolve("intellij.yml")
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
internal const val IDE_FILE = "intellij.yaml"
|
||||
internal const val IDE_FILE_VARIANT_2 = "intellij.yml"
|
||||
}
|
||||
+1
-4
@@ -59,10 +59,7 @@ internal class IntellijConfigurationJsonSchemaProviderFactory : JsonSchemaProvid
|
||||
|
||||
override fun isUserVisible() = false
|
||||
|
||||
override fun isAvailable(file: VirtualFile): Boolean {
|
||||
val nameSequence = file.nameSequence
|
||||
return StringUtil.equals(nameSequence, IDE_FILE) || StringUtil.equals(nameSequence, IDE_FILE_VARIANT_2)
|
||||
}
|
||||
override fun isAvailable(file: VirtualFile) = isConfigurationFile(file)
|
||||
})
|
||||
return (project as UserDataHolderBase).putUserDataIfAbsent(PROVIDER_KEY, result)
|
||||
}
|
||||
|
||||
+8
-1
@@ -15,7 +15,7 @@ import org.yaml.snakeyaml.nodes.SequenceNode
|
||||
|
||||
internal class RunConfigurationListReader(private val processor: (factory: ConfigurationFactory, state: Any) -> Unit) {
|
||||
// rc grouped by type
|
||||
fun read(parentNode: MappingNode) {
|
||||
fun read(parentNode: MappingNode, isTemplatesOnly: Boolean) {
|
||||
val keyToType = THashMap<String, ConfigurationType>()
|
||||
processConfigurationTypes { configurationType, propertyName, _ ->
|
||||
keyToType.put(propertyName.toString(), configurationType)
|
||||
@@ -28,6 +28,13 @@ internal class RunConfigurationListReader(private val processor: (factory: Confi
|
||||
continue
|
||||
}
|
||||
|
||||
if (keyNode.value == Keys.templates) {
|
||||
if (isTemplatesOnly) {
|
||||
read(tuple.valueNode as? MappingNode ?: continue, false)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
val configurationType = keyToType.get(keyNode.value)
|
||||
if (configurationType == null) {
|
||||
LOG.warn("Unknown run configuration type: ${keyNode.value}")
|
||||
|
||||
@@ -95,11 +95,33 @@ class ConfigurationFileTest {
|
||||
options.isAlternativeJrePathEnabled = true
|
||||
assertThat(result).containsExactly(options)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `one jvmMainMethod as list - template`() {
|
||||
val result = parse("""
|
||||
runConfigurations:
|
||||
templates:
|
||||
jvmMainMethod:
|
||||
- isAlternativeJrePathEnabled: true
|
||||
""", isTemplatesOnly = true)
|
||||
val options = ApplicationConfigurationOptions()
|
||||
options.isAlternativeJrePathEnabled = true
|
||||
assertThat(result).containsExactly(options)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `templates as invalid node type`() {
|
||||
val result = parse("""
|
||||
runConfigurations:
|
||||
templates: foo
|
||||
""", isTemplatesOnly = true)
|
||||
assertThat(result).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parse(@Language("YAML") data: String): List<Any> {
|
||||
private fun parse(@Language("YAML") data: String, isTemplatesOnly: Boolean = false): List<Any> {
|
||||
val list = SmartList<Any>()
|
||||
parseConfigurationFile(data.trimIndent().reader()) { _, state ->
|
||||
parseConfigurationFile(data.trimIndent().reader(), isTemplatesOnly) { _, state ->
|
||||
list.add(state)
|
||||
}
|
||||
return list
|
||||
|
||||
Reference in New Issue
Block a user