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:
Specifying where auto-completion should occur when implementing a completion contributor for a custom language.
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:
Factory for string and char pattern (see below); Logical operations like and, or, not
Factory for PSI-, IElement-, and VirtualFile-patterns
Patterns for PSI; Checks for children, parents, or neighboring leaves
Filter and check pattern collections; Mainly used to provide functionality for other high-level pattern classes
Patterns specifically for checking (PSI) tree structure
Check if strings match, have a certain length, have a specific beginning or ending, or are one of a collection of strings
Check if characters are whitespace, digits, or Java identifier parts
Some built-in languages in the IntelliJ Platform implement their own pattern classes and can provide additional examples:
XmlPatternsprovides patterns for XML attributes, values, entities, and texts.
PythonPatternsprovides patterns for literals, strings, arguments, and function/method arguments for Python.
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.
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.
The above pattern makes sure that the PSI element:
Appears after either an open bracket or a comma, which is expressed by putting a restriction on the neighboring leaf element.
JsonArrayas a level-two parent, which indicates that the PSI element must be inside a JSON array.
Does not have a
JsonStringLiteralas 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.
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:
Set breakpoints at the
Set a condition on the breakpoints that checks whether the string representation of the pattern contains an identifiable part of the pattern.
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.
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.
Using the Markdown code example from above, we note that the
MarkdownLinkDestinationImpl class is used in the element pattern. Now, set a breakpoint at:
Right-click on the breakpoint and set the following as a condition:
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
ReferenceProvidersRegistryImpl. This provides a good starting point for further investigation.