Most generic way to find range of a method

Question

Given a PsiElement, how to determine if it is (or is contained within) a function or method, and if so, determine the first and last PsiElement that defines this function or method?

Generic solution?

For example, when I invoke my plugin while the caret is on the keyword fun in Kotlin, I would like to select the entire method that has been declared (I understand some times this would fail, and in such cases I’ll have a fallback of a hard-coded amount of lines to expand).

It seems like this could be useful (from PsiTreeUtil):

public static @Nullable <T extends PsiElement> T getParentOfType(@Nullable PsiElement element, boolean strict,
                                                                   @NotNull Class<? extends T> @NotNull ... classes)

but it requires listing out all types of method PsiElements. I’d rather have something more generic that would find all methods and functions.
Psi stuff is based on languages, so I’m a bit lost in terms of abstractions. I understand not all languages have methods and functions (e.g. HTML, XML, etc.).
There is PsiMethod that, at first, seems very generic, but the documentation mentions that is actually only for Java methods. :frowning:

Example

To better illustrate my need:

kotlin
package com.foo.bar

import inc.bar.foo

class Wow {
  val x = "hmm"

  fun bing() { // START
    println("shh")
  } // END
}

If the caret is anywhere in between the start of the line marked with “START” and the end of the line marked with “END”, then I would like to select to entire method bing().

Alternative

Maybe there is a way to simply get the parent PsiElement ?
The main problem I see with that is that it’s hard to determine how far up the psi tree we need to go to get a relevant parent element.
For example, in the example above, if the caret is on "shh", isn’t the parent element println? That would not be good enough.

Potentially something hacky like checking the class name of the PsiElement and selecting anything that contains “Method” and “Function” in it?

There is no language-independent way to do this unfortunately, you need many language-specific APIs to do that

Do most languages (e.g. Java, Kotlin, Python, C#, JavaScript, TypeScript) end up having a common terminology/nomenclature for this?

If not, is there some repertory where I could quickly find the method/function classes for those languages?

I’m currently assuming these 6 languages will cover most of the usage of my plugin.

Also: where do I find what <depends> clauses I add to my plugin.xml to be able to reference these classes?

The PsiViewer plugin can help with this.

This information can be found in each product’s Product Specific page in the documentation. For PyCharm, it’s here.

Hmm, I think I’m going to explore a bit how I might be able to hack some kind of heuristic together using the FoldingModel. :thinking:

That could become the default fallback for the languages I don’t end up implementing, I guess.

Ended up going for this:

      val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(doc)
      val eolPsiElement = // increases likelihood to find a function
          psiFile?.findElementAt(doc.getLineEndOffset(doc.getLineNumber(editor.caretModel.offset)))
      var current = eolPsiElement
      while (current != null) {
        if (current is PsiDirectory) break // saves a few cycles, after checking entire file
        // TODO: support more languages
        if (current is JvmMethod || current is KtFunction) {
          val start = getLineRangeAtOffset(editor, current.textRange.startOffset).startOffset
          val end = getLineRangeAtOffset(editor, current.textRange.endOffset).endOffset
          return TextRange(start, end)
        }
        current = current.parent
      }

It seems to cover JVM languages. And then I have a fallback that uses foldingModel#getRegionsOverlappingWith and returns the TextRange of any fold with a max of 60 LOCs.

Then if that wasn’t enough, there’s a simple “up to 10 lines above and below caret offset” ultimate fallback.

If anyone has better ideas, feel free to share. :slight_smile:
Ultimately, I’m a bit bummed that there is no easy way to get the method definition that would come right before its enclosing folding range, but oh well… for now that’s good enough.

We usually introduce an extension point and implement it in language plugins in such cases. Cross-platform language analysis is truly cursed field and there are no good solutions there