Custom Language: References Tutorial

I've written Lexer & Parser for a custom language, following the Custom Language Support Tutorial [0]. I'd now like to implement clickable references for identifiers in my language, but the tutorial only explains ReferenceContributors [1], which I'd only need for references from other languages to mine, according to this forum post [2]:

> In your custom language, there is usually no need to go through ReferenceProvidersRegistry to provide references for an element. Instead, you can put all your logic directly into the getReferences() method of your PsiElement.

How to implement getReference() I'm missing some understandable example code, tough.

 

[0]: <http://www.jetbrains.org/intellij/sdk/docs/tutorials/custom_language_support_tutorial.html>

[1]: <http://www.jetbrains.org/intellij/sdk/docs/tutorials/custom_language_support/reference_contributor.html>

[2]: <https://intellij-support.jetbrains.com/hc/en-us/community/posts/206119409-Custom-language-tutorial-ReferenceContributor?page=1#community_comment_206134265>

1
4 comments

You're welcome to look at my implementation or ask me questions for IntelliJ Elixir, https://github.com/KronicDeth/intellij-elixir.  Just look for any time I mention Go To Declaration in the CHANGELOG (https://github.com/KronicDeth/intellij-elixir/blob/master/CHANGELOG.md).  You can click the PR links in the changelog to see exactly what I did to make it work.

2
Avatar
Permanently deleted user

Thanks Luke for your hint, I looked into PRs #198 and #452 but I'm not really seeing the relevant parts between all the layers of indirection (the reason why I asked for a tutorial in the first place, had tried to learn from other plugins before, too).

I've now implemented[1] recognition of definitions, i.e. PsiNameIdentifierOwner ([2] was quite helpful in understanding the meaning of that interface). However, my attempt[3] to resolve all *usages* to these definitions does not work & breaks the definitions by the way.

 

[1]: <https://github.com/azrdev/coco-idea-plugin/commit/282bc3638c8d0d4d9e3b14f5054211ed7593e41e>

[2]: <https://intellij-support.jetbrains.com/hc/en-us/community/posts/206103209-PsiNamedElement-vs-PsiNameIdentifierOwner>

[3]: <https://github.com/azrdev/coco-idea-plugin/commit/45f8c027e703dbb7a4da72f5fab4ace398876471>

0
Avatar
Permanently deleted user

My knowledge might not be complete or correct, but for the Mathematica plugin, I have a quite involved reference system working that resolves local usages, global usages and many other things. Therefore, let me explain how I understood this and how I implemented it and highlight some details I stumbled upon myself.

First things first: You don't need to "resolve usages". What you provide is the other way around. Your usages resolve to the defining element. So when you have this short code and your i is a variable PsiElement that provides a reference

int i;
print(i);
i++;

then each i should return a PsiReference on the getReference() call which resolves to the declaration i. This means, that the i in "int i" can resolve to itself. So what you do in your case is to write a class CocoPsiReference that implements the PsiReference interface. The most important method to implement is resolve() which should return the CocoNamedElement which is the declaration/definition. So instead of finding all usages, you make that each usage resolves to its definition/declaration point.

A PsiReferenceContributor is not required if you have set up this correctly. I used a ReferenceContributor to provide "additional" reference that don't need to be single PsiElements. So for instance if I have a string and I know that some words inside this string are usages of for instance a function, I can write a reference contributor for that which provides a PsiReference for the particular function names inside the string. These references would again resolve to the point of definition.

Let me give a working example:

As you can see, all myFunction are highlighted automatically because Idea can resolve the usages by itself. The only thing I have provided is that every myFunction returns a PsiReference that resolves to the first usage "myFunction::usage". The part of the string which is highlighted is again a reference but it is provided by an additional reference provider. In any case there is only one direction: Resolve from the usage to the definition.

The tricky part is how you find the correct place of declaration/definition when you see a variable. In Mathematica e.g. I can have local definitions of the same variable and they are resolved to their local context and not the global definition. See, that only the "inner" myFunction usages are highlighted although the name is the same.

I hope this helps.

6
Avatar
Permanently deleted user

Patrick, thanks for your explanations & sorry for the delay.

Your point about "resolving usages" was already clear to me, seems I was ambiguous when trying to summarize it briefly. Your words about the ReferenceContributor should be added to the tutorial/official documentation, although they only made clear to me that it's not relevant for coco at all.

 

In the meantime, I managed to get references working, partially, at least: Seems like my `CocoReference` extending `PsiReferenceBase` needs to override `getRangeInElement()`, too:

```

    @Override
    public TextRange getRangeInElement() {
        return TextRange.from(0, getElement().getTextLength());
    }

```

 

plus I had a bug in my resolving code.

Anyways, this lets the IDE highlight identifier usages & jump to their declaration using Ctrl+Click/Ctrl+B in a small test file. However, in a very large grammar I have here, highlighting doesn't work (but go to declaration does). Maybe my code is too slow & thus it aborts somewhere?

 

My addon is currently awaiting review, see <https://plugins.jetbrains.com/idea/plugin/9457-coco-r-grammar-support>

2

Please sign in to leave a comment.