Recommended way to get an injected language host from IFileElementType#doParseContents
Answered
I'm implementing `IFileElementType` for a custom language to get the parser context from the host element in injected language contexts, similar to how `RegExpFileElementType` works in intellij-community. The following works:
val host = InjectedLanguageUtil.findInjectionHost(psi)
in that `host` is not null. However, `InjectedLanguageUtil` is deprecated. If I use `InjectedLanguageManager` instead, as suggested, the following does not work:
val host = InjectedLanguageManager.getInstance(psi.project).getInjectionHost(psi)
in that `host` is null.
What is the recommended way to get the host of an injected language in this case?
Please sign in to leave a comment.
Hello. The recommended way is indeed
InjectedLanguageManager#getInjectionHost(psi)
or InjectedLanguageManager#getInjectionHost(viewProvider)
which implemented the similar way to INjectedLanguageUtil.
It's hard to say anything without the sources.
You could try the other InjectedLanguageManager#getInjectionHost(viewProvider) method and then if it fails, try to debug the INjectedLanguageUtil.findINjectedHost() and tell us where exactly the latter works whereas the former doesn't .
Thanks for the reply.
I've debugged the code using InjectedLanguageManager, and the call to FileContextUtil#getFileContext is returning null (as the INJECTED_IN_ELEMENT user data key is null). -- I suspect that this is because that key hasn't been set before the call to IFileElementType#doParseContents.
Looking at the InjectionRegistrarImpl logic in intellij-community, this does indeed look to be the case. The createAndRegisterInjected function is called from doneInjecting. That function is calling parseFile (which will ultimately call IFileElementType#doParseContents) first, and then is calling cacheEverything which is setting the INJECTED_IN_ELEMENT user data property, so doParseContents cannot see that user data property.
My doParseContents implementation is:
class XsltSchemaTypeFileElementType : IFileElementType(XsltSchemaTypes) {override fun doParseContents(chameleon: ASTNode, psi: PsiElement): ASTNode {
// val host = InjectedLanguageManager.getInstance(psi.project).getInjectionHost(psi) // returns null
val host = InjectedLanguageUtil.findInjectionHost(psi)
// ...
}
}
My MultiHostInjector implementation is:
class XsltSchemaTypeLanguageInjection : MultiHostInjector {
override fun elementsToInjectIn(): MutableList<out Class<out PsiElement>> {
return mutableListOf(XmlAttributeValue::class.java)
}
override fun getLanguagesToInject(registrar: MultiHostRegistrar, context: PsiElement) {
if (isIntellijXPathPluginEnabled())
return
XsltSchemaTypes.create(context)?.let { schemaType ->
val host = context as PsiLanguageInjectionHost
registrar.startInjecting(schemaType.language)
registrar.addPlace(null, null, host, host.textRange.let { it.shiftLeft(it.startOffset) })
registrar.doneInjecting()
}
}
}
>override fun doParseContents(chameleon: ASTNode, psi: PsiElement): ASTNode {
>val host = InjectedLanguageUtil.findInjectionHost(psi)
I think doParseContents() shouldn't depend on injection details (and thus shouldn't call findInjection() method)
Injections can be unavailable at the moment of doParseContents() call because they are computed asynchronously during highlighting, in a background thread, making this doParseCOntents() method non-determinstic.
That is what RegExpFileElementType in intellij-community is doing to get the regular expression capabilities for the lexer and parser (https://github.com/JetBrains/intellij-community/blob/6f5104f1a40b09c2f532c3e0ec730a463d4434b6/RegExpSupport/src/org/intellij/lang/regexp/RegExpFileElementType.java).
Ideally, I would like a way to pass the schemaType calculated in the MultiHostInjector class to the IFileElementType#doParseContents function, so I can avoid the need for repeating the logic in both areas.
I'm using this to avoid creating a separate language and language definition for each schema type (of which there are at least 15-20 types I am working on supporting), just like the regular expression support is using it to avoid creating a separate language for each regular expression variant.
Hmm. Looking at how RegEx and SQL are working, they are registering a new language and ParserDefinition for each dialect. I'll work on using a similar approach.
Thanks for the help and feedback.
OK.
(Just in case, I've made InjectedLanguageManager.getInjectionHost(psi) to behave the same as InjectedLanguageUtil.findInjectionHost(psi) for consistency)