Is it possible to use getNavigationElement on a PsiFile ? (stack trace in UsageInfo)

Answered

In my language, when a file has a function ‘make()’ I want to navigate to this function. 

For that, I have implemented the getNavigationElement method in my FileBase.java class (that inherits from PsiFileBase). If there is a make I return that PsiElement instead, something like this:

class FileBase inherits PsiFileBase {
  public PsiElement getNavigationElement() {
    if (isComponent()) {
      for (RPsiLet let : PsiTreeUtil.getStubChildrenOfTypeAsList(this, RPsiLet.class)) {
        if ("make".equals(let.getName())) {
          return let;
        }
    }
  }
  return super.getNavigationElement();
}

The problem is when a user is doing a ‘find in files’, the search is correct but the preview doesn’t show anything, and I got a stack trace like this one:

java.lang.IllegalArgumentException: element RPsiLet:make; startOffset 3341; endOffset=3351; effectiveStart=-490; effectiveEnd=-480; elementRange=(3831,8268); element.getTextOffset()=3835
        at com.intellij.usageView.UsageInfo.<init>(UsageInfo.java:71)
        at com.intellij.usageView.UsageInfo.<init>(UsageInfo.java:122)
        at com.intellij.find.impl.FindResultUsageInfo.<init>(FindResultUsageInfo.java:48)
        at com.intellij.find.impl.FindInProjectUtil.processSomeOccurrencesInFile(FindInProjectUtil.java:303)
        at com.intellij.find.impl.FindInProjectUtil.lambda$processUsagesInFile$4(FindInProjectUtil.java:265)
        at com.intellij.openapi.application.impl.ApplicationImpl.runReadAction(ApplicationImpl.java:941)
        at com.intellij.openapi.application.ReadAction.compute(ReadAction.java:68)
        at com.intellij.find.impl.FindInProjectUtil.processUsagesInFile(FindInProjectUtil.java:264)
        at com.intellij.find.impl.FindInProjectTask.processFindInFilesUsagesInFile(FindInProjectTask.java:251)
        at com.intellij.find.impl.FindInProjectTask.lambda$wrapUsageProcessor$6(FindInProjectTask.java:205)
        at com.intellij.find.impl.FindInProjectTask.lambda$findUsages$4(FindInProjectTask.java:154)
        at com.intellij.openapi.roots.impl.FilesScanExecutor.lambda$doProcessOnAllThreadsInReadAction$1(FilesScanExecutor.java:99)
        at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1154)
        at com.intellij.openapi.progress.util.ProgressIndicatorUtils.lambda$runInReadActionWithWriteActionPriority$0(ProgressIndicatorUtils.java:75)
        at com.intellij.openapi.progress.util.ProgressIndicatorUtils.runActionAndCancelBeforeWrite(ProgressIndicatorUtils.java:158)
        at com.intellij.openapi.progress.util.ProgressIndicatorUtils.lambda$runWithWriteActionPriority$1(ProgressIndicatorUtils.java:115)
        at com.intellij.openapi.progress.ProgressManager.lambda$runProcess$0(ProgressManager.java:66)
        at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$runProcess$2(CoreProgressManager.java:188)
        at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$executeProcessUnderProgress$12(CoreProgressManager.java:608)
        at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:683)
        at com.intellij.openapi.progress.impl.CoreProgressManager.computeUnderProgress(CoreProgressManager.java:639)
        at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:607)
        at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:60)
        at com.intellij.openapi.progress.impl.CoreProgressManager.runProcess(CoreProgressManager.java:175)
        at com.intellij.openapi.progress.ProgressManager.runProcess(ProgressManager.java:66)
        at com.intellij.openapi.progress.util.ProgressIndicatorUtils.runWithWriteActionPriority(ProgressIndicatorUtils.java:112)
        at com.intellij.openapi.progress.util.ProgressIndicatorUtils.runInReadActionWithWriteActionPriority(ProgressIndicatorUtils.java:75)
        at com.intellij.openapi.roots.impl.FilesScanExecutor.lambda$doProcessOnAllThreadsInReadAction$2(FilesScanExecutor.java:98)
        at com.intellij.openapi.roots.impl.FilesScanExecutor.lambda$processOnAllThreads$3(FilesScanExecutor.java:141)
        at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$runProcess$2(CoreProgressManager.java:188)
        at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$executeProcessUnderProgress$12(CoreProgressManager.java:608)
        at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:683)
        at com.intellij.openapi.progress.impl.CoreProgressManager.computeUnderProgress(CoreProgressManager.java:639)
        at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:607)
        at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:60)
        at com.intellij.openapi.progress.impl.CoreProgressManager.runProcess(CoreProgressManager.java:175)
        at com.intellij.openapi.roots.impl.FilesScanExecutor.lambda$runOnAllThreads$0(FilesScanExecutor.java:65)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at com.intellij.util.concurrency.BoundedTaskExecutor.doRun(BoundedTaskExecutor.java:241)
        at com.intellij.util.concurrency.BoundedTaskExecutor.access$200(BoundedTaskExecutor.java:31)
        at com.intellij.util.concurrency.BoundedTaskExecutor$1.execute(BoundedTaskExecutor.java:214)
        at com.intellij.util.ConcurrencyUtil.runUnderThreadName(ConcurrencyUtil.java:212)
        at com.intellij.util.concurrency.BoundedTaskExecutor$1.run(BoundedTaskExecutor.java:203)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
        at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:702)
        at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:699)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
        at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1.run(Executors.java:699)
        at java.base/java.lang.Thread.run(Thread.java:833)
        

 

The problem is that originalElement is the file and element is the navigationElement. Because element is different from originalElement a delta is computed and is negative (fileStartOffset - elementStartOffset)

Is it possible to have a navigationElement for a PsiFile, and if yes, what method should I implement to not get the exception ?

0
5 comments

Hi Giraud,

I understand that you want to navigate to the make function when you navigate to a file from usages in the editor with the “Go to declaration” action.

If it is the case, please try implementing com.intellij.codeInsight.TargetElementEvaluatorEx2.getGotoDeclarationTarget() (and register it in com.intellij.targetElementEvaluator extension point) to return the make() function if it exists.

0

I’ll try and let you know, thank you

0

It works for this use case, thanks.

I still have few more questions if you don’t mind:

- I have other PsiElements (not files) that implement this getNavigationElement() method. should I remove it and always use the TargetElementEvaluatorEx2 instead ? when getNavigationElement should be used ?

- How to test navigation with fixtures ? before I would just do myFixture.getElementAtCaret() and get the  PsiElement of the function, now I get the file.

0

Sure.

1. According to method's Javadoc:

Returns the PSI element which should be used as a navigation target when navigation to this PSI element is requested. The method can either return this or substitute a different element if this element does not have an associated file and offset. (For example, if the source code of a library is attached to a project, the navigation element for a compiled library class is its source class.)

It seems that the purpose of this method is to override it in cases when the element doesn't have a physical PSI representation ("does not have a file and offset"). In your case, elements do have files and offsets (even if it is 0), so it doesn't apply. I suggest using TargetElementEvaluator for other elements too.

2. You can try the following approach (Kotlin code):

val testedFile = myFixture.addFileToProject(filePath, code).virtualFile
myFixture.configureFromExistingVirtualFile(testedFile)
val elementAtCaret = myFixture.elementAtCaret
val declarationElement = TargetElementUtil.getInstance().getGotoDeclarationTarget(elementAtCaret, elementAtCaret.navigationElement)
// assert that declarationElement is either file (if no main()) or main()
0

Please sign in to leave a comment.