FindUsage for PsiNamedElement with multiple PsiElement children

Hi all,
I'm in process of implementing the FindUsage feature for the Robotframework plugin but unfortunately I'm a bit stuck...

As suggested in the wiki I have implemented a class implementing PsiNamedElement for a keyword definition and another one extending PsiReference for each keyword usage.

Everything goes smoothly when I trigger a FindUsage action on the PsiReference. On the contrary, when I trigger a FindUsage on the keyword definition I get this message:
     Cannot search for usages. Position to an element to find usages for, and try again.

The problem appears to be that the PsiElement under the caret is not recognised as a PsiNamedElement.

To give a bit of a context, a Robotframework keyword is usually composed by many words separated by space and often comprising also some variable. Like:

Given some ${variable} applies

This keyword is tokenized in my plugin like this:

|Given| |some| |${variable}| |applies|

With tokens respectively of type word, whitespace, word, whitespace,word, whitespace, word, whitespace, variable ... etc

The resulting PsiTree is something like

KeywordDefinition extends ASTWrapperPsiElement implements PsiNamedElement
    |
    |----LeafPsiElement(Word)
    |----LeafPsiElement(Whitespace)
    |----LeafPsiElement(Word)
    |----LeafPsiElement(Whitespace)
    |----LeafPsiElement(Variable)
    |----LeafPsiElement(Whitespace)
    |----LeafPsiElement(Word)

As I debug the code I found out that this is caused by the check TargetElementUtil.java line 161:

if (parent.getTextOffset() == element.getTextRange().getStartOffset() && !(parent instanceof XmlAttribute)         && !(parent instanceof PsiFile && InjectedLanguageManager.getInstance(parent.getProject()).isInjectedFragment((PsiFile)parent))) {         return parent; }


Now it seems to me that I would have to override the getTextOffset() method in my KeywordDefinition (extending PsiNamedElement) as suggested in the wiki, but my problem is that the offset should be different based on the offset of the LeafPsiElement that is currently under the caret.

In fact the Find usage is already working if I put the caret in the first word of the keyword (this is the only case in which the aforementioned equality is satisfied)

Any clue on how I could solve the problem?

Kind regards and thanks in advance for the support!

Valerio

14 comments
Comment actions Permalink

Hi Valerio,

It's hard to understand what's going on from description. Is it opensourced plugin? I could look at it and say what's the problem.

0
Comment actions Permalink

Hi Alexander,

thanks a lot for your support.
The plugin is open source and you can find it on GitHub.

Please checkout the find_usage branch at the address:

https://github.com/AmailP/robot-plugin/tree/find_usage

Please get back to me for every detail that you need.

Cheers,

Valerio

0
Comment actions Permalink

Hi Valerio,

I've cloned and run your project and I've realised that you're trying to implement gherkin language. Actually IDEA already supports it and it is open sourced. You can find the core of language support in 'cucumber' repository: https://github.com/JetBrains/intellij-plugins/tree/master/cucumber

Also you can find opensourced plugins that support interaction (completion/resolving/running) with java (https://github.com/JetBrains/intellij-plugins/tree/master/cucumber-java) and groovy (https://github.com/JetBrains/intellij-plugins/tree/master/cucumber-groovy).

So I believe that robot frameworks support requires from you only registering GherkinFileType for *.robot files and implementing of dozens interfaces for interaction with target language.

0
Comment actions Permalink

Returning to original problem. I need some code samples with declaration and reference to it. Likely that you just don't implement isReferenceTo method (but it's just guess). Also I can advice you to use CompositePsiElement instead of ASTWrapperPsiElement, it's implementing almost all methods that you need for find usages.

0
Comment actions Permalink

Hey :)

you are right, the Robot Framework language looks quite similar to Gherkins but in fact the syntax is a bit different.

Here below a sample .robot file that you can use as reference to try the find usages:

*** Test Cases ***
Test title
    Keyword number one    value    second value
    When action
    Keyword number one    other value    more values
    Then check expectations

*** Keywords ***
Keyword number one
    Things to be done ...


In this case there is one keyword definition (amailp.intellij.robot.psi.KeywordDefinition):

Keyword number one     Things to be done ...


And two usages of the just defined keyword (amailp.intellij.robot.psi.Keyword):

    Keyword number one    value    second value
    Keyword number one    other value    more values


If you "Find usage" on one of the psi.Keyword they are correctly found, but this does not happen to the KeywordDefinition.

I will have a look anyway to the CompositePsiElement. Is there some documentation around? When shoud I prefer it to ASTWrapperPsiElement?

0
Comment actions Permalink

Hi,

I was wrong about using CompositePsi, it doesn't fit for your plugin.

I've just debug your plugin, it seems that it's not really easy to solve your problem. IMHO, the most proper way (with minimum side effects) is to reimplement lexer: represent whole keyword name just with one single token. In this case you fix'll find usages issue and rename bug.

Offtopic:

Btw, using your own whitespace element instead of PsiWhitespace could lead to strange errors. It makes sense to use your own whitespaces in lexer and remap them to the standard ones in the parser (see ITokenTypeRemapper for details).

Also it makes sense to use -Didea.is.internal=true in your run configuration. In this case you'll be able to invoke inner actions (e.g. show psi structure of current file) and sometimes get more debug information in stdout.

0
Comment actions Permalink

Yes, I was thinking to the solution proposed by you, of lexing the keywords as single tokens, but in this case I fear that some feature of the language would become really hard to implement.

In particular in robot framework is possible to define keywords with embedded variables of the form:

*** Keywords *** Given a ${color} shape     Create a shape of color    ${color}


In this case I'm defining a keyword that would match various keyword usage. E.g.:

Given a red shape
Given a yellow shape


You can think of it like a method call with a formal parameter.

In cases like this I think that would really make sense to have multiple tokens matching then to multiple PSI elements. This could allow for example to easily rename the ${color} variable. Does this make sense to you?

Thanks a lot for your time :)

Valerio

0
Comment actions Permalink

Indeed it makes sense for me. Although ${}-interpolations you can implement via injections. Anyway if you want to save 'multitoken-keyword-name-lexing' you can play with PomTargets, e.g. try to implement your own PomDeclarationSearcher that will retrieve DelegatePsiTarget(keywordName) for each word in keyword name.

0
Comment actions Permalink

For what it's worth, the Gerhkin syntax also allows for the use of 'variables' via scenario outlines/examples, so that plugin might be worth checking out still
Some end user documentation demonstrating this support is here - https://www.jetbrains.com/idea/webhelp/creating-examples-table-in-scenario-outline.html

0
Comment actions Permalink

Hmm, interesting, I saw that scenario's name in gherkin is one single token. Well I can ask how this functionality had been implemented. Or you can investigate it by yourself in repository :)

UPD: sorry, I didn't notice that author of comment is not the author of topic :) Anyway it is the good point to investigate how cucumber plugins works.

0
Comment actions Permalink

I will have a look at the PomTargets then even though I haven't found much documentation about them. Can you point me towards some resources? What are they useful for? What pom stands for?
In the first place I had ignored the com.intellij.pom pachage since it seemed to be kinda deprecated:

from /Applications/IntelliJ IDEA 13.app/lib/openapi.jar!/com/intellij/pom/package.html

The POM API is unfinished, undocumented, does not allow to perform any tasks for which no other
APIs exist, and thus should not be used.

0
Comment actions Permalink

Well, indeed we have no any documentation of POM api. The only useful resource that allows you to figure out what it is and how it could be used is https://github.com/JetBrains/intellij-community. I've pointed you at PomDeclarationSearcher, I believe that this entity will be enough for your issue.
Also please look at cucumber plugin, as Alan Foster mentioned, gherkin also have interpolations inside step references but it uses 'single-token' approach.

0
Comment actions Permalink

Hey,

I have played a bit (and debugged a lot :) ) in the weekend with the PomDeclarationSearcher/DelegatePsiTarget as you suggested and these are the results:

  • I have created and registered a custom PomDeclarationSearcher
  • when a KeywordName is reached during the recursive PSI tree ascending I create a DelegatePsiTarget(KeywordDefinition) and pass it to the Consumer<PomTarget>
  • now seems that is correctly identified as a Usage target
  • the DelegatePsiTarget (holding the KeywordDefinition) is automatically wrapped in a PomTargetPsiElementImpl
  • now the JavaFindUsagesHandlerFactory greedly wants to handle all objects that are instance of PsiMetaOwner, so i had to create an register a custom RobotFindUsagesHandlerFactory that recognises my wrapped KeywordDefinition and unwarps it for further use. Anyway the check in /Users/angelini/src/idea/java/java-impl/src/com/intellij/lang/java/JavaFindUsagesProvider.java:53:

     element instanceof PsiMetaOwner && ((PsiMetaOwner)element).getMetaData() != null;

     looks to me a bit too loose to me, since PsiMetaOwner seems quite unrealated to Java so as far as I can see it should not be managed by a JavaFindUsagesProvider.

Anyway now the FindUsage seems to work well from both Keyword and KeywordDefinition.

Still remains an issue regarding the renaming:

  • Everything works smoothly if I trigger a rename from a Keyword usage.
  • When I trigger a rename from a KeywordDefinition a dialog opens asking to rename a Delegate Psi Target and then nothing is renamed. I guess that I have to register something to handle this special type (RenameHandler or RenamePsiElementProcessor). Today I will give it a try.


Thank you anyway for your support and please give me your feedback if you think I should simplify/rework my approach (the find usage branch is updated with the almost working code ;) )

Cheers

Valerio

0
Comment actions Permalink

Hi,

looks to me a bit too loose to me, since PsiMetaOwner seems quite unrealated to Java so as far as I can see it should not be managed by aJavaFindUsagesProvider.

It's ok for JavaFindUsagesProvider because it's language extension, the bad thing is that JavaFindUsagesProvider#canFindUsagesFor is invoked from JavaFindUsagesHandlerFactory without language checking. Anyway your fix with introducing your own findUsageProviderFactory is totally right.

  • When I trigger a rename from a KeywordDefinition a dialog opens asking to rename a Delegate Psi Target and then nothing is renamed. I guess that I have to register something to handle this special type (RenameHandler or RenamePsiElementProcessor). Today I will give it a try.


You can try to use RenameableDelegatePsiTarget instead of DelegatePsiTarget or extend DelegatePsiTarget with your own implementaion with appropriate setName method.

0

Please sign in to leave a comment.