Language injection with extra characters

Answered

I'm writing a plugin for supporting embedded PHP code in YAML from https://github.com/nelmio/alice/blob/master/doc/advanced-guide.md#identity using com.intellij.psi.LanguageInjector. The YAML string looks like that:

<(strtolower("BAR"))>

To turn the string into valid PHP code, I need to trim "<(" and ")>" and add "<?php return " before the code and ";" after it. The latter can be done with the arguments of injectionPlacesRegistrar.addPlace(), but I can't find a way to ignore the first two and the last two characters. If I just use narrower TextRange, I get the following error:

After patch: doc:
'<?php return strtolower("BAR");'
---PSI:
'<?php return <(strtolower("BAR"))>;'
---chars:
'<?php return <(strtolower("BAR"))>;'

It seems like injection can only be done for the whole PsiLanguageInjectionHost. Is there a way to deal with those extra characters?

5 comments
Comment actions Permalink

You need to implement and register an element manipulator for your element which will allow to select the range in host.

If you implement in your host PsiLanguageInjectionHost.createLiteralTextEscaper() then you can change almost anything to anything between the host element text and your injected element text, with a bit of work and lots of debugging.

<extensions defaultExtensionNs="com.intellij">
  <lang.elementManipulator forClass="com.vladsch.idea.multimarkdown.psi.element.MdVerbatim" implementationClass="com.vladsch.idea.multimarkdown.psi.manipulator.MdVerbatimManipulator"/>
</extensions>  
public class MdVerbatimManipulator implements MdElementManipulator<MdVerbatim> {
    /**
     * Changes the element's text to a new value
     *
     * @param element    element to be changed
     * @param range      range within the element
     * @param newContent new element text
     *
     * @return changed element
     *
     * @throws IncorrectOperationException if something goes wrong
     */
    @Override
    public MdVerbatim handleContentChange(@NotNull MdVerbatim element, @NotNull TextRange range, String newContent) throws IncorrectOperationException {
        return handleContentChange(element, newContent);
    }

    @Override
    public MdVerbatim handleContentChange(@NotNull MdVerbatim element, String newContent) throws IncorrectOperationException {
        return (MdVerbatim) element.setContent(newContent);
    }

    @NotNull
    @Override
    public TextRange getRangeInElement(@NotNull MdVerbatim element) {
        return element.getContentRange(false);
    }
}

1
Comment actions Permalink

BTW, in my element.getContentRange(false) means get range relative to element not absolute offsets in the file. The range should be contained within the element's .getText() range: 0..length()

0
Comment actions Permalink

I forgot to mention that YAML file is already parsed by an IDEA's plugin, and I want to inject a language into an existing PsiLanguageInjectionHost (YAMLScalarImpl), which already has its ElementManipulator. Should I modify the PSI elements and replace them with my classes? Or should I just override the ElementManipulator?

0
Comment actions Permalink

I thought about that and was not sure if that was the case. I don't know if you can override it. I think the IDE will use the first one registered.

You might want to try creating a FakePsiElement for the YAML element you are working with and creating a manipulator for your fake element class which changes the range returned by the original manipulator and delegates the handle content change call back to it.

If that does not work then change the range to make it shorter in your manipulator, but for handle content change call to original, add the characters you left out of your range to newContent and pass the original range of the YAML manipulator so the original manipulator sees everything as it expects from the IDE.

I don't know how the original manipulator is written so not sure whether it can handle partial range content changes.

1
Comment actions Permalink

Thanks, Vladimir. I managed to do it by creating a FakePsiElement and overriding decode() and getOffsetInHost() of the original LiteralTextEscaper.

0

Please sign in to leave a comment.