Injecting fields in groovy script

已回答

Hi

I am developing a plugin that will allow auto-completion of non-existent fields in a groovy script (Lets ignore whether it compiles or not).

Searching and reading lead me to:

<extensionPoint name="membersContributor" interface="org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor"/>

which I am using this way:

public void processDynamicElements(@NotNull PsiType qualifierType,
                           PsiClass aClass,
                           @NotNull PsiScopeProcessor processor,
                           @NotNull PsiElement place,
                           @NotNull ResolveState state) {
LightFieldBuilder someString = new LightFieldBuilder("someString", String.class.getName(), aClass); processor.execute(someString, state);
LightFieldBuilder someInt = new LightFieldBuilder("someInt", Integer.class.getName(), aClass);
processor.execute(someInt, state); }

I get someString and someInt in auto-competion popup with proper types String and Integer, but methods of those fields always match field type passed to processor.execute as first one. In this exmaple that would be String.

In other words:

someString. // now intellij suggests methods for type String, which is good

someInt. // also suggests String methods, like replace() etc.

 

If I swap the order, I would get Integer methods for both. What am I doing wrong?

P.S. I do not really get all the PSI stuff yet.

P.S2. I cannot do it with gdsl, names and types of those fields will be dynamically resolved from current script.

0
正式评论

For each reference there is a processor created.
Both fields are passed to each processor, so first field always wins.

You need to ask a processor about a reference being resolved currently. This is done via name hint `org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil#getNameHint`.

Example:

String name = ResolveUtil.getNameHint(processor);
if (name == null) {
// we are in completion, so feed the processor all available elements
}
else if (name.equals("someString")) {
// feed someString field
}
else if (name.equals("someInt")) {
// feed someInt field
}

 

Avatar
Permanently deleted user

Thank you Daniil for explanation, its working.

0

I found this post and I have an additional question.

When I implement the case above, IntellIJ suggest me "someString" and "someInt" as global variable. Additionally it suggest them as members of any variable.

So when I type "someString." IntelliJ suggest me to write "someString.someString" (and going on like "someString.someString.someString" and so on).

How can I test if the code completion is invoked for a "global" variable and not for a member of another variable?

0

Hi Dominik,

There is an `aClass` parameter, which means the receiver of resolved property/method.
For example in `"foo".someString` the receiver would be a PsiClass representing java.lang.String class.
In case of just `someString` the receiver would be `this` type.

If `someString` reference is located in some class CoolClass then `this` would be an instance of PsiClass representing CoolClass:

class CoolClass {
  def someMethod() {
    someString // <- here the receiver (or this) is a PsiClass of CoolClass
  }
}

If `someString` reference is located in a script, then `this` from Groovy perspective would mean a class which is generated from the script.
There is a special PsiClass to represent script class: org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass, you need to check if aClass is instance of GroovyScriptClass.

someString // <- here the receiver (or this) is a GroovyScriptClass of the script file
0

Thanks Daniil, 

works perfectly.

0

请先登录再写评论。