Extend a language through a LanguageSubstitutor


I recently started to work hard on a Language/Framework plugin (precisely https://github.com/Tolc/IntelliJ_Jahia_plugin).
So far I am loving it and I am learning a lot of things on the way.



For this plugin, I need to 'extend' the Properties Language.

What I mean by 'extending' is that I don't want to re-parse the Properties files, and I want them to act just as plain basic Properties files (i.e. not losing any feature like the ResourceBundles grouping, etc), and put my own logic on top of it.


Precisely, the framework I am working on declares nodetypes (within namespaces), and relies on Properties files for i18n translation (pretty basic).
So the Properties files are a mix of plain old 'key=translation', and framework related 'namespaceKey_nodetypeKey=translation'.
Basically something like this:

namespace_nodetype=nodeTypeTranslation //translation for nodetype namespace:nodetype
namespace_nodetype2=nodeType2Translation //translation for nodetype namespace:nodetype2

The thing is the Properties language WordsScanner considers that 'namespace_nodetype' is a word, so the property does not appear in my nodetype usages, (of course therefore not being renamed when I rename the nodetype, for instance)...
I would need it to be processed as two separate words: 'namespace' and 'nodetype'.


1. First try: custom FindUsagesProvider

At first I've tried to declare my own FindUsagesProvider for the Properties Language (returning a custom WordsScanner that processes the file text as I want it to be processed).

<lang.findUsagesProvider language="Properties" order="first" implementationClass="fr.tolc.jahia.intellij.plugin.cndExtendedProperties.CndExtendedPropertiesFindUsagesProvider"/>

But this is never actually called... 
I guess it is the expected behaviour?
(I have another FindUsagesProvider in the same plugin for a custom language that is being called, so I can't see why this one would not be)


2. Second try: LanguageSubstitutor

Then I tried implementing a LanguageSubtitutor.
As I understood it, that would normally just fit my needs, right?


So I declared my new Language, its BaseLanguage being PropertiesLanguage (I don't know if the SyntaxHighlighter part is needed though):

public class CndExtendedPropertiesLanguage extends Language {
public static final CndExtendedPropertiesLanguage INSTANCE = new CndExtendedPropertiesLanguage();

private CndExtendedPropertiesLanguage() {
super(PropertiesLanguage.INSTANCE, "CndExtendedProperties", "text/properties");

SyntaxHighlighterFactory.LANGUAGE_FACTORY.addExplicitExtension(this, new SingleLazyInstanceSyntaxHighlighterFactory() {
protected SyntaxHighlighter createHighlighter() {
return new PropertiesHighlighter();


Then I declared the ExtendedPropertiesLanguageSubstitutor:

<lang.substitutor language="Properties" implementationClass="fr.tolc.jahia.intellij.plugin.cndExtendedProperties.PropertiesToCndExtendedProperties"/>
public class PropertiesToCndExtendedProperties extends LanguageSubstitutor {
public Language getLanguage(@NotNull VirtualFile file, @NotNull Project project) {
//check if file is in a module (exclude libraries)
if (FileIndexFacade.getInstance(project).getModuleForFile(file) != null) {
return CndExtendedPropertiesLanguage.INSTANCE;
return null;

That part is functional, the .properties files are treated as containing CndExtendedPropertiesLanguage. But that is not quite what I wanted.


From what I have understood, as I need a CndExtendedProperties file to be and act as a Properties file as well, I need to use a custom FileViewProvider, extending MultiplePsiFilesPerDocumentFileViewProvider, so that the VirtualFile could have multiple PsiFile (here precisely two: one for the PropertiesLanguage, and one for my CndExtendedPropertiesLanguage).

<lang.fileViewProviderFactory language="CndExtendedProperties" implementationClass="fr.tolc.jahia.intellij.plugin.cndExtendedProperties.CndExtendedPropertiesFileViewProviderFactory"/>
public class CndExtendedPropertiesFileViewProviderFactory implements FileViewProviderFactory {
public FileViewProvider createFileViewProvider(@NotNull VirtualFile file, Language language, @NotNull PsiManager manager, boolean eventSystemEnabled) {
assert language.isKindOf(CndExtendedPropertiesLanguage.INSTANCE);
return new CndExtendedPropertiesFileViewProvider(manager, file, eventSystemEnabled);
public class CndExtendedPropertiesFileViewProvider extends MultiplePsiFilesPerDocumentFileViewProvider {

public CndExtendedPropertiesFileViewProvider(PsiManager manager, VirtualFile file, boolean eventSystemEnabled) {
super(manager, file, eventSystemEnabled);

public Language getBaseLanguage() {
return PropertiesLanguage.INSTANCE;

protected MultiplePsiFilesPerDocumentFileViewProvider cloneInner(VirtualFile fileCopy) {
return new CndExtendedPropertiesFileViewProvider(getManager(), fileCopy, false);

public Set<Language> getLanguages() {
return new THashSet<>(Arrays.asList(new Language[]{PropertiesLanguage.INSTANCE, CndExtendedPropertiesLanguage.INSTANCE}));


And at the same time I changed my FindUsagesProvider so that it would work on CndExtendedPropertiesLanguage:

<lang.findUsagesProvider language="CndExtendedProperties" implementationClass="fr.tolc.jahia.intellij.plugin.cndExtendedProperties.CndExtendedPropertiesFindUsagesProvider"/>


But at this point, my debug IntelliJ instance executing the plugin started to get really unstable when trying to open/display .properties files, with this exception numerous times in the logs: 

ERROR - llij.ide.plugins.PluginManager - Argument for @NotNull parameter 'root' of com/intellij/codeInsight/daemon/impl/analysis/HighlightingSettingsPerFile.getHighlightingSettingForRoot must not be null

and of course my FindUsagesProvider still not being called.


So I guess there's something wrong with my code, or concepts that I might have not quite got right. Or both.


Recap / Questions:

So basically what I am trying to achieve here precisely is to register a custom FindUsagesProvider for the PropertiesLanguage, so that the property line appear in my nodetype usages.
I am sorry for the big wall of text, but I guess that was necessary to clearly show what I tried and what I am willing to do.

  • Is it normal that my first try at declaring a FindUsagesprovider was not called?
  • Is there just a simpler way I am not aware of?


I would really need some explanation/clarification on the LanguageSubstitutor and FileViewProvider logic, as for another feature of the plugin I will need to 'extend'/'enrich' the JSP language as well:

  • Is it possible to extend/enrich an existing language with it, without having to re-do the lexing/parsing? (as I am not really 'adding' anything to the language, I feel like it would not be necessary) 
  • When do you want to declare a new Language with a BaseLanguage? (I tried with and without and it did not seem to change anything)


Thanks in advance to any IntelliJ plugin guru that would be able to help me.


Comment actions Permalink

I'm having a similar issue. I'm trying to provide support for a specific annotation-based framework for Java and I got nearly everything working correctly, except Find Usages.

No method other than getWordsScanner() gets called.

Comment actions Permalink

I think I understand now why that doesn't work.
The provider doesn't actually relate to the logic for finding the usages, only how to present the found usages to the user in terms of labels, etc.

The actual logic for finding usages is in the *UsageHandler classes, it seems. It also seems to be quite a big task to replace that mechanism.

Comment actions Permalink

Perhaps something here can help us:




The source code for the Struts2 plugin might help with that, from what I've seen.

Comment actions Permalink

Oops. Only now I noticed this is from 2016.

Sorry about the necro, guys :-/

Comment actions Permalink

Any success here?I'm actually stuck with the same problem: I want to implement custom FindUsagesProvider for JSON files and not affect any other JSON-type feature coming from JSON-plugin. 

Comment actions Permalink

So, I ended up creating something that extends a UsageTypeProvider instead. It works like a charm.

I'll post the link to its source code as soon as I can open source it ;-)


Please sign in to leave a comment.