IntelliJ Platform Plugin SDK Help

Element Patterns

Element patterns provide a generic way to specify conditions on objects. Plugin authors use them to check whether PSI elements match a particular structure. Just as regular expressions for strings test whether a (sub-)string matches a particular pattern, element patterns are used to put conditions on the nested structure of PSI elements. Their two main applications inside the IntelliJ Platform are:

  1. Specifying where auto-completion should occur when implementing a completion contributor for a custom language.

  2. Specifying PSI elements that provide further references via a PSI reference contributor.

However, plugin authors rarely implement the ElementPattern interface directly. Instead, we recommend using the high-level pattern classes provided by the IntelliJ Platform:

ClassMain ContentsNotable Examples
StandardPatternsFactory for string and char pattern (see below); Logical operations like and, or, notLogbackReferenceContributor, RegExpCompletionContributor
PlatformPatternsFactory for PSI-, IElement-, and VirtualFile-patternsFxmlReferencesContributor, PyDataclassCompletionContributor
PsiElementPatternPatterns for PSI; Checks for children, parents, or neighboring leavesXmlCompletionContributor
CollectionPatternFilter and check pattern collections; Mainly used to provide functionality for other high-level pattern classesPsiElementPattern
TreeElementPatternPatterns specifically for checking (PSI) tree structurePyMetaClassCompletionContributor
StringPatternCheck if strings match, have a certain length, have a specific beginning or ending, or are one of a collection of stringsAbstractGradleCompletionContributor
CharPatternCheck if characters are whitespace, digits, or Java identifier partsCompletionUtil

Some built-in languages in the IntelliJ Platform implement their own pattern classes and can provide additional examples:

  • XmlPatterns provides patterns for XML attributes, values, entities, and texts.

  • PythonPatterns provides patterns for literals, strings, arguments, and function/method arguments for Python.

  • DomPatterns builds upon XmlPatterns and acts as a wrapper to provide further patterns for DOM-API.

Examples

A good starting point for element patterns is the Custom Language Support Tutorial. They are used in the completion and reference contributor section of the tutorial. However, the IntelliJ Platform source code provides many more examples of element patterns for built-in languages like JSON, XML, Groovy, Markdown, and so on. Checking the references in the table above or searching for usages of the high-level pattern classes will provide a comprehensive list that shows how element patterns are used in production code.

For instance, an example can be found in MarkdownReferenceProvider that tests if a PSI element is an instance of the MarkdownLinkDestinationImpl class and appears in a Markdown file.

PsiElementPattern.Capture<MarkdownLinkDestinationImpl> linkDestinationCapture = psiElement(MarkdownLinkDestinationImpl.class). inFile(psiFile(MarkdownFile.class));

As shown in the code above, element patterns can be stacked and combined to create more complex conditions. JsonCompletionContributor contains another example with more requirements on the PSI element.

PsiElementPattern.Capture<PsiElement> AFTER_COMMA_OR_BRACKET_IN_ARRAY = psiElement(). afterLeaf("[", ","). withSuperParent(2,JsonArray.class). andNot( psiElement(). withParent(JsonStringLiteral.class) );

The above pattern makes sure that the PSI element:

  1. Appears after either an open bracket or a comma, which is expressed by putting a restriction on the neighboring leaf element.

  2. Has JsonArray as a level-two parent, which indicates that the PSI element must be inside a JSON array.

  3. Does not have a JsonStringLiteral as a parent, which prevents situations where a string with a bracket or comma inside an array would give a false-positive match.

This last example shows that corner cases need to be considered carefully even for simple patterns.

Tools and Debugging

Working with element patterns can be tricky, and plugin authors need a solid understanding of the underlying PSI structure to get it right. Therefore, it is recommended to use the PsiViewer plugin or the built-in PSI viewer and verify that elements indeed have the expected structure and properties.

Debugging

For this section, it is assumed that plugin authors have a basic understanding of how to work with a debugger, how to set breakpoints, and how to set conditions on breakpoints.

When debugging element patterns, plugin authors need to keep in mind that the places where element patterns are instantiated are unrelated to where they are actually used. For instance, while patterns for completion contributors are instantiated when registering the contributor, the patterns are checked during completion while typing. Therefore, finding the correct locations in the IntelliJ Platform for debugging element patterns is the first important step.

However, setting breakpoints inside ElementPattern will result in many false-positives since element patterns are used extensively throughout the IDE. One way to filter out these false-positives is to use a condition on the breakpoints. The following steps can help you investigate where patterns are checked:

  1. Set breakpoints at the ElementPattern.accepts() methods.

  2. Set a condition on the breakpoints that checks whether the string representation of the pattern contains an identifiable part of the pattern.

  3. Debug, and when the breakpoint triggers, make sure it is the right pattern and investigate the call stack to find relevant methods that use the pattern check.

  4. Debug the relevant methods, e.g. methods that fill completion variants or find references.

Note that finding an identifiable part of a pattern can be achieved by setting a breakpoint where the pattern is instantiated and checking its string representation.

Debugging Example

Using the Markdown code example from above, we note that the MarkdownLinkDestinationImpl class is used in the element pattern. Now, set a breakpoint at:

com.intellij.patterns.ElementPattern#accepts( java.lang.Object, com.intellij.util.ProcessingContext )

Right-click on the breakpoint and set the following as a condition:

toString().contains("MarkdownLinkDestinationImpl")

Now start a debug session and open a Markdown file. When the breakpoint hits, the call stack in the debug tool window shows that reference-providers are checked in the method doGetReferencesFromProviders within ReferenceProvidersRegistryImpl. This provides a good starting point for further investigation.

Last modified: 23 June 2021