Is it possible to change the Editor language / content type dynamically?
已回答
I'm wondering if it's possible to change the language of the Editor after it was instantiated? E.g. I create an editor as follows:
EditorFactory.getInstance().createEditor(f.getViewProvider().getDocument(), getProject(), language.getAssociatedFileType(), false)
and the language at that point was XML, how can I change it to JSON without creating a new Editor?
请先登录再写评论。
Hi Eugene,
It's hard to answer without knowing a real problem you're trying to solve and without knowing the context where you're going to change the language of a created editor.
Likely you want to implement `com.intellij.psi.LanguageSubstitutor` extension.
OK, let me explain this in more details. I'm working on a custom plugin which allows conversions between various different data formats, e.g. between XML, JSON, CSV, etc. I created a custom editor for the conversion script, which has a main window with the script itself plus multiple tabs for inputs and outputs. Those tabs don't have actual files associated with them, I create PSI files from text, as follows:
The whole setup looks like this:
Now here's the tricky part. Sometimes I may want to change the mime type of the input directive, e.g. from application/xml to application/json. In this case I want to locate the appropriate tab and change the formatting, syntax highlighting and validation on the fly. I did figure out the syntax highlighting part, but formatting, folding and validation still don't work as expected.
> I did figure out the syntax highlighting part, but formatting, folding and validation still don't work as expected.
I think that file reparsing will help (FileContentUtilCore#reparseFiles), but it shouldn't be like that. I mean it's not a good way to make things work by fixing basic features one by one. There are just too many features to care about.
I still recommend you to implement a LanguageSubstitutor which will decide what language should be used for a particular file. Just be careful in selecting files to handle, of course, your plugin should not try to substitute language for any file in a project.
The similar approach is used for Scratch files, here is the ScratchFileService that contains substitutor-extension and configuration mapping between files and corresponding languages. And here is LanguageAction located, which changes the language for a scratch file.
Yes, language mapping is not an issue, I already figured that piece out, but I'm still not quite sure how do I update the language in Editor and/or in the PsiFile.
From the LanguageAction code it seems like every time the language is changed, a new file is created, is that correct? So is it correct that there's no way to update the language in existing PsiFile and Editor and the new one has to be created?
> From the LanguageAction code it seems like every time the language is changed, a new file is created, is that correct?
Indeed, looks like that particular example is not the best one. Couldn't find any better in community.
> So is it correct that there's no way to update the language in existing PsiFile and Editor and the new one has to be created?
No, it's not correct. Sorry, I don't know how to say it in a different way, so I just repeat: I believe that you need a language substitutor and per file mapping to keep language setting for a file. Reparsing file might be required on changing mapping.
Hmmm, tried to reparse file, didn't help...
Details needed. Have you tried to debug it? Does the language substitutor handle file correctly and select proper language?
I'll try to debug it further.
Sorry, for reopening the old conversation but I still haven't found the way to change the VirtualFile type on the fly. Here's what happens: I do have a way to resolve the language and get an instance of the Language class based on my settings. Here's my code
However, the vfile.getFileType still returns an old type. E.g. I created file as XML, then change language to JSON, but the logger output is still:
*** NEW FILE TYPE IS com.intellij.ide.highlighter.XmlFileType@391c708c
So my question remains, how do I change the file type of a VirtualFile on the fly?
Hi Eugene,
It's not supposed to invoke LanguageSubstitutors.INSTANCE.substituteLanguage manually unless you do this from some FileViewProvider. You should implement your own LanguageSubstitutor extension. So the answer is the same: implement substitutor and reparse file there if needed.
Oh, sorry, it seems you changed the language.
> So my question remains, how do I change the file type of a VirtualFile on the fly?
There is no way. VitualFile#getFileType has nothing to do with a language. Actually, it has nothing to do with PSI, it's just a type of file on the virtual file system. If you have `test.xml` file is still XML file whatever the language is inside it.
The thing is, that particular file is not on the file system, I instantiated it as follows:
PsiFile f = PsiFileFactory.getInstance(getProject()).createFileFromText(language, someDataString)
What I'm trying now is to create a new PsiFile and set the editor to its content, but it still doesn't work as expected.
So have you implemented language substitutor or not?
No, I'm still trying to figure out how to implement it based on my situation. I may need a bit more time.
I'd recommend you to read the code of IntelliJ platform and debug all things that you're trying to do.
For example, take a look at https://github.com/JetBrains/intellij-community/blob/master/platform/core-api/src/com/intellij/psi/LanguageSubstitutors.java#L45, it does NOT substitute language, it asks substitutors to do this. If you didn't implement one – nothing happens. So the invokation you have shown me before does exactly nothing.
The answer is still the same: implement a substitutor which will know what languages in which files should be replaced based on some configuration. Or recreate the PSI file and an editor.
Yep, looks like I can't implement the substitutor because I can't find a way to tell it what is the new language for the file. The language is determined on the fly based on the user input. Not sure how the substitutor will fit this.
You still can store the mapping between file and target language somewhere and read this mapping in a language substitutor.The easiest way to do it is to store the language selected by
The easiest way is to store the language selected by user in "PsiFile#putUserData" and then check it (PsiFile#getUserData) in a language substitutor.
OK, so now I implemented a substitutor as a public static inner class of my custom editor (which is also a public class) and registered it in plugin.xml, but when I run it, I get java.lang.ClassNotFoundException.
I cannot say anything by class name of exception. Stacktrace, content pluign.xml, content of your LanguageSubstitutor class might be helpful
here's the exception:
java.lang.RuntimeException: java.lang.ClassNotFoundException: org.mule.tooling.lang.dw.editor.WeaveEditor.WeaveIOSubstitutor PluginClassLoader[org.mule.tooling.intellij.dataweave, 0.8]
at com.intellij.lang.LanguageExtensionPoint$1.compute(LanguageExtensionPoint.java:45)
at com.intellij.openapi.util.NotNullLazyValue.getValue(NotNullLazyValue.java:39)
at com.intellij.lang.LanguageExtensionPoint.getInstance(LanguageExtensionPoint.java:53)
at com.intellij.openapi.util.KeyedExtensionCollector.buildExtensions(KeyedExtensionCollector.java:155)
at com.intellij.openapi.util.KeyedExtensionCollector.buildExtensions(KeyedExtensionCollector.java:128)
at com.intellij.openapi.util.KeyedExtensionCollector.forKey(KeyedExtensionCollector.java:116)
at com.intellij.psi.LanguageSubstitutors.substituteLanguage(LanguageSubstitutors.java:46)
at com.intellij.lang.LanguageUtil.getLanguageForPsi(LanguageUtil.java:57)
at com.intellij.psi.impl.file.impl.FileManagerImpl.createFileViewProvider(FileManagerImpl.java:283)
at com.intellij.psi.impl.file.impl.FileManagerImpl.findViewProvider(FileManagerImpl.java:224)
at com.intellij.psi.impl.file.impl.FileManagerImpl.findFile(FileManagerImpl.java:406)
at com.intellij.psi.impl.PsiManagerImpl.findFile(PsiManagerImpl.java:182)
at com.intellij.lang.properties.editor.ResourceBundleEditorProvider.lambda$accept$0(ResourceBundleEditorProvider.java:48)
at com.intellij.openapi.application.ReadAction.compute(ReadAction.java:53)
at com.intellij.lang.properties.editor.ResourceBundleEditorProvider.accept(ResourceBundleEditorProvider.java:48)
at com.intellij.openapi.fileEditor.impl.FileEditorProviderManagerImpl$2.compute(FileEditorProviderManagerImpl.java:94)
at com.intellij.openapi.fileEditor.impl.FileEditorProviderManagerImpl$2.compute(FileEditorProviderManagerImpl.java:88)
at com.intellij.openapi.application.impl.ApplicationImpl.runReadAction(ApplicationImpl.java:892)
at com.intellij.openapi.fileEditor.impl.FileEditorProviderManagerImpl.getProviders(FileEditorProviderManagerImpl.java:88)
at com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl.openFileImpl4(FileEditorManagerImpl.java:829)
at com.intellij.openapi.fileEditor.impl.EditorsSplitters$UIBuilder.processFiles(EditorsSplitters.java:861)
at com.intellij.openapi.fileEditor.impl.EditorsSplitters$UIBuilder.processFiles(EditorsSplitters.java:816)
at com.intellij.openapi.fileEditor.impl.EditorsSplitters$ConfigTreeReader.process(EditorsSplitters.java:807)
at com.intellij.openapi.fileEditor.impl.EditorsSplitters.openFiles(EditorsSplitters.java:235)
at com.intellij.openapi.fileEditor.impl.OpenFilesActivity.runActivity(OpenFilesActivity.java:35)
at com.intellij.ide.startup.impl.StartupManagerImpl.lambda$runPostStartupActivitiesFromExtensions$1(StartupManagerImpl.java:146)
at com.intellij.ide.startup.impl.StartupManagerImpl.runActivity(StartupManagerImpl.java:344)
at com.intellij.ide.startup.impl.StartupManagerImpl.runPostStartupActivitiesFromExtensions(StartupManagerImpl.java:150)
at com.intellij.openapi.project.impl.ProjectManagerImpl.lambda$openProject$6(ProjectManagerImpl.java:339)
at com.intellij.openapi.project.impl.ProjectManagerImpl.openProject(ProjectManagerImpl.java:353)
at com.intellij.openapi.project.impl.ProjectManagerImpl$2.compute(ProjectManagerImpl.java:412)
at com.intellij.openapi.project.impl.ProjectManagerImpl$2.compute(ProjectManagerImpl.java:403)
at com.intellij.openapi.progress.Task$WithResult.run(Task.java:307)
at com.intellij.openapi.progress.impl.CoreProgressManager$TaskRunnable.run(CoreProgressManager.java:710)
at com.intellij.openapi.progress.impl.CoreProgressManager$11.run(CoreProgressManager.java:423)
at com.intellij.openapi.progress.impl.CoreProgressManager$3.run(CoreProgressManager.java:179)
at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:568)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:519)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:54)
at com.intellij.openapi.progress.impl.CoreProgressManager.runProcess(CoreProgressManager.java:164)
at com.intellij.openapi.application.impl.ApplicationImpl.lambda$null$9(ApplicationImpl.java:569)
at com.intellij.openapi.application.impl.ApplicationImpl$2.run(ApplicationImpl.java:309)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ClassNotFoundException: org.mule.tooling.lang.dw.editor.WeaveEditor.WeaveIOSubstitutor PluginClassLoader[org.mule.tooling.intellij.dataweave, 0.8]
at com.intellij.ide.plugins.cl.PluginClassLoader.loadClass(PluginClassLoader.java:64)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at com.intellij.openapi.extensions.AbstractExtensionPointBean.findClass(AbstractExtensionPointBean.java:42)
at com.intellij.openapi.extensions.AbstractExtensionPointBean.instantiate(AbstractExtensionPointBean.java:63)
at com.intellij.openapi.extensions.CustomLoadingExtensionPointBean.instantiateExtension(CustomLoadingExtensionPointBean.java:47)
at com.intellij.lang.LanguageExtensionPoint.access$000(LanguageExtensionPoint.java:28)
at com.intellij.lang
plugin.xml has this:
The code is:
Subclasses must be referenced via "$" in plugin.xml:
Ahhh, you are right, please pardon my stupidity :)
Well, this is getting ugly. I put the new type into the PsiFile, but in the substitutor I only have access to the VirtualFile. So I'm calling PsiManager.findFIle() which is calling the substitutor, causing circular reference and stack overflow. I have a gut feeling that the approach I'm taking is completely wrong...
Then put it in VirtualFile, it's also UserDataHolder