Annotator and nested levels local variable highlighting

It is again about the Mathematica Plugin (GitHub page) and I try to highlight nested levels of local variables. In this answer Dmitry said, that third-party plugins should use the Annotator extension in preference to HighlightingVisitor. Let me give two small examples of what I try to achieve:
First

Module[{var1,var2},
  Block[{var1},
    var1+var2
  ]
]


Module and Block are both scoping constructs to localize variables. As you see, var1 gets shadowed by the inner Block which is perfectly fine. Module variables get different colors than Block variables and therefore the inner var1 should be colorized as Block-variable. Second example

f[var_]:=Module[{var}, code]


Here, the argument to the function f which is defined is var. This var gets shadowed by the local Module var which is not fine and therefore the var in Module should be colorized as error.
Therefore, my Annotator has two requirements:

  1. It needs to annotate the code traversing top-down the PsiTree. This could be implemented by checking whether the PsiElement in com.intellij.lang.annotation.Annotator#annotate is the root file node and then annotating in a recursive manner. The question is, when I type code, does IDEA call the annotate function only for the chaged parts of the PsiTree? Are there other options?
  2. I need to store information on my recursive descent namely: Which variable are already defined in outer constructs.


Any help, hints or code-links are highly appriciated!

Cheers
Patrick

9 comments

The framework gives no guarantees on the order of element traversal, and will not call annotate() for all elements in the file if the user makes a local change. However, If I understand your requirements correctly, the annotator can work just fine regardless of the order in which IntelliJ IDEA calls it. Each time annotate() is called on a specific element, you can walk the tree up from it and see what names are defined there and whether they're being shadowed by the name of the current element. Of course, you can cache the list of names in each element; the cache needs to be cleared when IntelliJ IDEA calls PsiElement.clearCaches().

0
the cache needs to be cleared when IntelliJ IDEA calls PsiElement.clearCaches().

Slightly off topic here, sorry, but this is something I've not heard before. Is this also true of elements cached with the CachedValuesManager?

0

No. For a cached value created using CachedValuesManager, you specify the dependencies, and the value gets invalidated once any of the dependencies changes.

0

Ok, so when you're talking about caching values above, you mean values that are simply stored in user data? Are there any pros and cons to the two caching methods (i.e. any guidance as to when to choose one or the other)?

0

OK, I actually got confused and the actual story is a bit different. :) The clearCaches() method is not part of the general PsiElement implementation and is specific to certain PSI implementations (such as Java). What's common is the subtreeChanged() method which is available in all PSI elements which extend ASTDelegatePsiElement. The method is called when any element below this element is modified, and it is intended to be used to clear the caches stored directly in this element (as fields or user data).

If you're writing a custom language implementation and your cached data depends only on the data directly under your PSI element (e.g. you're caching the list of methods in a class), it's easier to clear caches in subtreeChanged(). If you depend on values outside of your PSI element, you need to use CachedValue or another caching mechanism.

0

Ah, ok, thanks Dmitry, that's very interesting. I might have to modify how I'm doing some of my caching then - currently I'm only using CachedValuesManager. Does using CachedValuesManager introduce much overhead, i.e. is it worth going back and modifiying existing code that works?

While we're on the subject, I was also wondering how the dependencies of a CachedValue signal that they've changed? I tried to trace through the code, but it's a little... baroque. Is this done through ModificationTracker? i.e. if I'd like to make one of my objects participate as a dependency, is implementing ModificationTracker sufficient?

0

I don't think there's much overhead in using CachedValueManager.

Implementing ModificationTracker may not even be necessary. The code that you're interested in is CachedValueBase.getTimeStamp() and PsiCachedValue.getTimeStamp().

1

Perfect, thanks Dmitry, I'll take a look.

0
Avatar
Patrick Scheibe

Dmitry, Colin,

thanks for the discussion. I thought I don't disturb and just listen and learn.

Cheers
Patrick

0

Please sign in to leave a comment.