Adding CSS properties programmatically to register them as valid

Hey there,

Randori (http://randoriframework.com) uses a couple of vendor specific CSS properties to attach behaviour and mediators (-randori-behaviour, -randori-mediator, etc) to HTML views.

I've been able to add our properties programmatically to the code completion using a CompletionContributor, yet, IntelliJ still marks the -randori properties
as invalid in the editor. (I.e. it draws a squigly line beneath it and reports that it is invalid).

Is there a way where I can somehow register these extra properties so that they are perceived as valid by the IDE?

thanks,

Roland Zwaga

15 comments

Hi Roland,

At the moment css-api is not really handy (it will be improved in the next version). However you can do what you want.

First of all, you should implement CssElementDescriptorProvider ('com.intellij.css.elementDescriptorProvider' extension point).

Important methods for your issue:
1. isMyContext() should return true if your provider should be used in given context.
2. getPropertyDescriptor() – returns instance of CssPropertyDescriptor (descriptor of property with given name). Almost all inspections are using this method.
3. getPropertiesNames() – returns list of available properties in given context. Method is used by completion (your custom CompletionContributor will not need after implementation of this method).

Unfortunately, base implemenation of CssElementDescriptorProvider is unavailable in API, but you can retrieve it from ExtensionPoint (as a rule the base implementation will be the last one):

    CssElementDescriptorProvider[] providers = CssElementDescriptorProvider.EP_NAME.getExtensions();
    return providers.length > 0 ? providers[providers.length - 1] : null;

You should use the base implementation in your methods to get descriptors for properties that IDEA already knows.
After that, delegate the rest of methods of your provider to base provider implementation and everything should work.

0

Hi Alexander,

thank you very much for this info, I'll be trying it out soon.

Thanks!

Roland

0

Hi Alexander,

I'm pretty sure I'm registering my extension point wrong, my CssElementDescriptorProvider isn't being invoked at all, its not even being created.
I've registered the extension like this:

<com.intellij.css.elementDescriptionProvider implementation="randori.plugin.lang.css.RandoriCssElementDescriptorProvider"/>

I'm guessing this is faulty, do you have any idea what I'm doing wrong?

cheers,

Roland Zwaga

0

Try add 'order' attribute:


<com.intellij.css.elementDescriptionProvider implementation="randori.plugin.lang.css.RandoriCssElementDescriptorProvider" order="first"/>

0

Hi Alexander,

alas, this makes no difference, I don't see the provider getting created anywhere, nor any of its methods invoked.

Just to be clear, what I did was, I created a class that extends CssElementDescriptorProvider, i.e.:

public class RandoriCssElementDescriptorProvider extends CssElementDescriptorProvider

Then I added a private method that retrieves the base implementation, like this:


private CssElementDescriptorProvider getBaseProvider()
{
    if (_base == null)
    {
        CssElementDescriptorProvider[] providers = CssElementDescriptorProvider.EP_NAME.getExtensions();
        _base = providers.length > 0 ? providers[providers.length - 1] : null;
    }
    return _base;
}


Like you proposed, in most methods I simply delegate to the base provider, only adding my own logic in the methods you specified.

Finally, I added the <com.intellij.css.elementDescriptionProvider implementation="randori.plugin.lang.css.RandoriCssElementDescriptorProvider" order="first"/>
element to my plugin.xml as you suggested.

Unfortunately, at this moment the RandoriCssElementDescriptorProvider doesn't even get instantiated, so I must still be screwing something up
in my configuration...

cheers,

Roland

0

Hi Roland,

Could you push your changes in some custom branch at https://github.com/RandoriAS/randori-plugin-intellij. I'll try figure out what happens.

0

Hey Alexander,

I've pushed to my fork of the plugin:

https://github.com/rolandzwaga/randori-plugin-intellij/tree/CSSCompletion

Thank you VERY much for your help!

cheers,

Roland

0

I've looked at code and there is two problems in plugin.xml.

1. extension point has name 'elementDescriptorProvider' instead of 'elementDescriptionProvider';
2. you declared extension inside <extensions> tag with namespace 'com.intellij' (defaultExtensionNS attribute). It means that you should define you extension without com.intellij prefix.

This is the right definition:


<css.elementDescriptorProvider implementation="randori.plugin.lang.css.RandoriCssElementDescriptorProvider" order="first"/>




And some minor issues. IDEA SDK has many useful util classes. E.g. in CompletionContributor you can use PsiTreeUtil, StringUtil and LookupElementBuilder classes and reimplement addLookupElements() and getCssDeclarations() methods in following way:



private void addLookupElements(Collection<IClassDefinition> subClasses, CompletionResultSet result) {
    for(final IClassDefinition subClass : subClasses) {
        result.addElement(LookupElementBuilder.create(StringUtil.quote(subClass.getQualifiedName())
                .withPresentableText(subClass.getQualifiedName())));
    }
}


private CssDeclaration getCssDeclaration(PsiElement position) {
    return PsiTreeUtil.getParentOfType(position, CssDeclaration.class);
}


Message was edited by: Alexander Zolotov

0

Hey Alexander,

ok, that Descriptor vs. Description mistake is slightly embarassing, I managed to completely miss that one, sorry...
As for the prefix, I should have caught that one too, I'm still learning the conventions of IntelliJ plugin development I suppose.

Thanks a lot for the pointers about thhose utl classes as well, they'll come in handy I'm sure.

So, thank you very, very much for your assistance!

cheers,

Roland

0

Hi Alexander,

I've got most of the CssElementDescriptorProvider working, I just ran into one issue that I can't really figure out...
When retrieving the list of possible values for my Randori CSS properties I need a reference to my ProjectComponent.
I get this reference through first retrieving the current project, the code that we use for this is as follows:


public static Project getProject()
{
    try
    {
        AsyncResult<DataContext> dataContext = DataManager.getInstance()
                .getDataContextFromFocus();
        Project project = PlatformDataKeys.PROJECT.getData(dataContext
                .getResult());
        return project;
    }
    catch (IllegalArgumentException e)
    {
         // Happens when the project is closing.
    }
    return null;
}


Unfortunately this method yields null when invoked from my CssElementDescriptorProvider, because the

dataContext.getResult()invocation returns null.

Is there perhaps some other way that I could get a reference to my project?

Thank you!

cheers,

Roland

0

Aha, nevermind the above question, I can get a reference to the project through the PsiElement.getProject() method.

Everything is working the way I need right now, thanks a lot for all your help!

0

Yes, PsiElement#getProject() is the right way.

You are welcome. Feel free to ask something else about api, I'll try to help.

0

Hey Alexander,

well, since you offered :) I wil ask :)

In Randori we use these custom CSS properties to define behaviours that can be attached to HTML elements (among other things).
So, we have CSS that looks like this:

.hmssApp {
    -randori-context: "startup.DemoContext";
    -randori-mediator: "mediators.IndexMediator";
    -randori-behavior: "behaviors.EchoBehavior";
}



So, the property values there are actual class names (as you can see of course), (Randori allows you to write these classes in Actionscript
which gets cross-compiled to JavaScript, the JS framework then handles the actual creation and injection of the behaviours, mediators, etc).

Anyways, the last bit of functionality that would really boost a bit of productivity is if users would be able to control+click the property value (i.e. class name)
and jump to that file.

If you can give me a few hints on which class to extend or which interface to implement for this, I'd be extremely grateful once again :)
I basically just need to, somehow, catch that control-click and see if it was on a PsiElement that represents a class name. (the rest I can figure out myself)
Or, at the very least, you could tell me that this just isn't possible right now, in which case it will save me some time searching for an answer :)

Thank you again!

Roland
0

Well, you should implement custom ReferenceContributor and provide reference to Java classes for each CssString element that belongs to randori-specific property. About ReferenceContributor you can read in our tutorial: http://confluence.jetbrains.com/display/IntelliJIDEA/Reference+Contributor

About creating references. I'm not sure that it is the good way (I'm not working with java support), but I would have done it in following way:


public class MyCustomReferenceContributor() {

//blablalba

     final String text = cssString.getValue(); //return value without quotes
     final JavaClassReferenceProvider provider = new JavaClassReferenceProvider();
     // you can add resolving options here via setOption(), see CustomizationKey at https://github.com/JetBrains/intellij-community/blob/master/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/JavaClassReferenceProvider.java
     return provider.getReferencesByString(range.substring(text), cssString, 1); //1 is the start offset in element, without first quote

//blablabla


0

Thanks Alex, that's probably enough info for me to get started, thank you!

cheers,

Roland

0

Please sign in to leave a comment.