FindUsage for PsiNamedElement with multiple PsiElement children Follow
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
Please sign in to leave a comment.
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.
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
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.
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.
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):
And two usages of the just defined keyword (amailp.intellij.robot.psi.Keyword):
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?
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.
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:
In this case I'm defining a keyword that would match various keyword usage. E.g.:
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
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.
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
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.
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
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.
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:
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:
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
Hi,
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.
You can try to use RenameableDelegatePsiTarget instead of DelegatePsiTarget or extend DelegatePsiTarget with your own implementaion with appropriate setName method.