platform: make lang substitutor dynamic IDEA-226264

GitOrigin-RevId: 53f525ee77d1391b04a32632e36c258f71c98ba4
This commit is contained in:
anstarovoyt
2019-12-19 13:07:12 +00:00
committed by intellij-monorepo-bot
parent 0ebc97022c
commit d3759e6942
6 changed files with 139 additions and 24 deletions
@@ -0,0 +1,43 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.lang
import com.intellij.ide.plugins.loadExtensionWithText
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.fileTypes.PlainTextLanguage
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.LanguageSubstitutor
import com.intellij.psi.PsiManager
import com.intellij.testFramework.UsefulTestCase
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
class LanguageSubstitutorLoadUnloadTest : LightJavaCodeInsightFixtureTestCase() {
fun testBefore() {
myFixture.configureByText("dummy.txt", "package hello;")
}
fun testLoadUnload() {
val beforeLoading = myFixture.configureByText("dummy.txt", "package hello;")
UsefulTestCase.assertInstanceOf(beforeLoading.language, PlainTextLanguage::class.java)
val virtualFile = beforeLoading.virtualFile
val text = "<lang.substitutor language=\"TEXT\" implementationClass=\"${TextToJavaSubstitutor::class.java.name}\"/>"
val disposable = loadExtensionWithText(text, javaClass.classLoader)
val psiManager = PsiManager.getInstance(myFixture.project)
val afterLoading = psiManager.findFile(virtualFile)
UsefulTestCase.assertInstanceOf(afterLoading!!.language, JavaLanguage::class.java)
Disposer.dispose(disposable)
val afterUnloading = psiManager.findFile(virtualFile)
UsefulTestCase.assertInstanceOf(afterUnloading!!.language, PlainTextLanguage::class.java)
}
private class TextToJavaSubstitutor : LanguageSubstitutor() {
override fun getLanguage(file: VirtualFile,
project: Project): Language? {
return if (file.name.startsWith("dummy")) JavaLanguage.INSTANCE else null
}
}
}
@@ -9,6 +9,9 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileDocumentManagerListener;
import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
@@ -34,6 +37,7 @@ import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.impl.PsiManagerImpl;
import com.intellij.psi.impl.PsiTreeChangeEventImpl;
import com.intellij.util.FileContentUtilCore;
import com.intellij.util.KeyedLazyInstance;
import com.intellij.util.messages.MessageBusConnection;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
@@ -66,6 +70,31 @@ public final class PsiVFSListener implements BulkFileListener {
@Override
public void runActivity(@NotNull Project project) {
MessageBusConnection connection = project.getMessageBus().connect();
ExtensionPoint<KeyedLazyInstance<LanguageSubstitutor>> point = LanguageSubstitutors.getInstance().getPoint();
if (point != null) {
point.addExtensionPointListener(
new ExtensionPointListener<KeyedLazyInstance<LanguageSubstitutor>>() {
@Override
public void extensionRemoved(@NotNull KeyedLazyInstance<LanguageSubstitutor> extension,
@NotNull PluginDescriptor pluginDescriptor) {
if (project.isDisposed()) return;
PsiManagerImpl psiManager = (PsiManagerImpl)PsiManager.getInstance(project);
((FileManagerImpl)(psiManager.getFileManager())).processFileTypesChanged(true);
}
@Override
public void extensionAdded(@NotNull KeyedLazyInstance<LanguageSubstitutor> extension,
@NotNull PluginDescriptor pluginDescriptor) {
if (project.isDisposed()) return;
PsiManagerImpl psiManager = (PsiManagerImpl)PsiManager.getInstance(project);
((FileManagerImpl)(psiManager.getFileManager())).processFileTypesChanged(true);
}
}, false, project);
}
connection.subscribe(ProjectTopics.PROJECT_ROOTS, new MyModuleRootListener(project));
connection.subscribe(FileTypeManager.TOPIC, new FileTypeListener() {
@Override
@@ -32,14 +32,16 @@ public class FileBasedIndexSwitcher {
FileBasedIndexImpl.LOG.assertTrue(mySemaphore.isUp());
mySemaphore.down();
for (Project project : ProjectManager.getInstance().getOpenProjects()) {
DumbService.getInstance(project).queueTask(new DumbModeTask() {
@Override
public void performInDumbMode(@NotNull ProgressIndicator indicator) {
indicator.setText(IdeBundle.message("progress.indexing.reload"));
mySemaphore.waitFor();
}
});
if (!ApplicationManager.getApplication().isUnitTestMode()) {
for (Project project : ProjectManager.getInstance().getOpenProjects()) {
DumbService.getInstance(project).queueTask(new DumbModeTask() {
@Override
public void performInDumbMode(@NotNull ProgressIndicator indicator) {
indicator.setText(IdeBundle.message("progress.indexing.reload"));
mySemaphore.waitFor();
}
});
}
}
myFileBasedIndex.performShutdown(true);
@@ -411,7 +411,7 @@
<extensionPoint name="codeInsight.surroundWithRangeAdjuster" interface="com.intellij.codeInsight.generation.surroundWith.SurroundWithRangeAdjuster" dynamic="true"/>
<extensionPoint name="lang.substitutor" beanClass="com.intellij.lang.LanguageExtensionPoint">
<extensionPoint dynamic="true" name="lang.substitutor" beanClass="com.intellij.lang.LanguageExtensionPoint">
<with attribute="implementationClass" implements="com.intellij.psi.LanguageSubstitutor"/>
</extensionPoint>
@@ -0,0 +1,56 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.plugins
import com.intellij.ide.plugins.DynamicPlugins.loadPlugin
import com.intellij.ide.plugins.DynamicPlugins.unloadPlugin
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.io.FileUtil
import com.intellij.testFramework.assertions.Assertions
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import kotlin.math.abs
fun loadDescriptorInTest(dir: Path): IdeaPluginDescriptorImpl {
Assertions.assertThat(dir).exists()
PluginManagerCore.ourPluginError = null
val parentContext = DescriptorListLoadingContext.createSingleDescriptorContext(emptySet())
val result = DescriptorLoadingContext(parentContext, /* isBundled = */ false, /* isEssential = */ true,
PathBasedJdomXIncluder.DEFAULT_PATH_RESOLVER).use { context ->
PluginManagerCore.loadDescriptorFromFileOrDir(dir, PluginManagerCore.PLUGIN_XML, context, Files.isDirectory(dir))
}
if (result == null) {
@Suppress("USELESS_CAST")
Assertions.assertThat(PluginManagerCore.ourPluginError as String?).isNotNull
PluginManagerCore.ourPluginError = null
}
return result!!
}
@JvmOverloads
fun loadExtensionWithText(extensionTag: String, loader: ClassLoader, ns: String = "com.intellij"): Disposable {
val name = "test" + abs(extensionTag.hashCode())
val text = """<idea-plugin>
<name>$name</name>
<extensions defaultExtensionNs="$ns">
$extensionTag
</extensions>
</idea-plugin>"""
return loadPluginWithText(text, loader)
}
fun loadPluginWithText(pluginXml: String, loader: ClassLoader): Disposable {
val directory = FileUtil.createTempDirectory("test", "test", true)
val plugin = File(directory, "/plugin/META-INF/plugin.xml")
FileUtil.createParentDirs(plugin)
FileUtil.writeToFile(plugin, pluginXml)
var descriptor = loadDescriptorInTest(plugin.toPath().parent.parent)
descriptor.setLoader(loader)
loadPlugin(descriptor, false)
return Disposable {
descriptor = loadDescriptorInTest(plugin.toPath().parent.parent)
unloadPlugin(descriptor, false)
}
}
@@ -379,19 +379,4 @@ private val testDataPath: String
private fun loadDescriptorInTest(dirName: String): IdeaPluginDescriptorImpl {
return loadDescriptorInTest(Paths.get(testDataPath, dirName))
}
internal fun loadDescriptorInTest(dir: Path): IdeaPluginDescriptorImpl {
assertThat(dir).exists()
PluginManagerCore.ourPluginError = null
val parentContext = DescriptorListLoadingContext.createSingleDescriptorContext(emptySet())
val result = DescriptorLoadingContext(parentContext, /* isBundled = */ false, /* isEssential = */ true, PathBasedJdomXIncluder.DEFAULT_PATH_RESOLVER).use { context ->
PluginManagerCore.loadDescriptorFromFileOrDir(dir, PluginManagerCore.PLUGIN_XML, context, Files.isDirectory(dir))
}
if (result == null) {
@Suppress("USELESS_CAST")
assertThat(PluginManagerCore.ourPluginError as String?).isNotNull()
PluginManagerCore.ourPluginError = null
}
return result!!
}