referenceSearch EP and the optimizations under the hood


I had problems with reference-search before when variables contained non-java symbols like $ that are allowed in my language. I have implemented the referenceSearch EP and used a PsiSearchHelper that did not use the option


Still, I now ran into another issue. In the following code, the first "var" (the underscore is an operator and not part of the variable!) is the target. It refers to itself and both of the two remaining "var" also refer to it.

var_ :> var_ + var

I have written a small action that simply checks all references within a file and the output for the above code is (-> means "resolves to")

"var" at offset 1 -> "var" at offset 1 (is Ref: true, soft: false)
"var" at offset 9 -> "var" at offset 1 (is Ref: true, soft: false)
"var" at offset 16 -> "var" at offset 1 (is Ref: true, soft: false)

However, the second "var_" which acts as "usage" and not as a target, doesn't get highlighted.

I suspect the error is within the optimized string-search that IntelliJ does before it checks the locations for valid usages.

Does some know, where exactly these string-occurrences of "var" are collected within the IdentifierHighlighterPass, so that I can debug, where things go south? Of course, if someone has a solution, I'm all ears!


1 comment
Comment actions Permalink


I debugged it with a clear head and I have found what I'm doing wrong, but I don't understand why this is wrong. I'm using the following PsiSearchHelper which uses a TextOccurenceProcessor

    PsiSearchHelper helper = PsiSearchHelper.getInstance(target.getProject());
if (helper instanceof PsiSearchHelperImpl) {

TextOccurenceProcessor processor = (symbol, offsetInElement) -> {
if (symbol instanceof Symbol) {
if (Objects.equals(((Symbol) symbol).resolve(), target)) {
// return false;
return true;

EnumSet<PsiSearchHelperImpl.Options> options = EnumSet.of(
((PsiSearchHelperImpl) helper).processElementsWithWord(

The wrong line was the "return false" which I called once my processor had found the correct place. I thought, once I found the right place, I should return false to stop the processor from further work. However, if I do this, then processTreeUp function inside LowLevelSearchUtil will return null instead of the lastElement (line 104)

if (!processor.execute(run, start)) {
return null;

This leads to that processElementsAtOffsets (line 183) does not iterate through all found offsets

for (int offset : offsetsInScope) {
lastElement = processTreeUp(project, scope, searcher, offset, processInjectedPsi, progress, lastElement, processor);
if (lastElement == null) return false;

I guess I don't understand how this all is supposed to work, but at least now it appears to work.


Please sign in to leave a comment.