References within elements - substrings
A reference doesn't need to be applied to the complete range of an element - it doesn't have to be the whole string in a string literal. Multiple references can be applied to different parts of a single element.
For example, a file path reference provider will create multiple references for substrings within the string, one for each file path segment. A reference which wishes to do this should implement the
IReferenceWithinElement<T> interface, where
T is the owning PSI tree node.
The constructor for
ElementRange takes in an instance of
T and the
TreeTextRange that is the range within the node that should be used for the reference. The range should be relative to the length of the owner node's short name.
Instead of implementing the
IReferenceWithinElement<T> interface directly, you can derive from
ReferenceWithinElementBase<TOwner, TToken>, which provides most of the implementation for you. This class derives from
TreeReferenceBase but implements things a little differently.
The constructor takes in the two element nodes, and the relative
TreeTextRange of the range within the
TToken tree node parameter. The name of the reference, as returned by
GetText is the substring of the
TToken node defined by this range. The range of the reference comes from the relative tree range of the substring.
ReferenceWithinElementBase provides an implementation of the abstract
ResolveWithoutCache. It defers to the abstract
GetReferenceSymbolTable. However, after retrieving the symbol table, it applies filters returned from
GetSymbolFilters. This allows our implementation of
GetReferenceSymbolTable to be very straight forward - we can return the symbol table for a particular class, for example, and then use the filters to reduce it to appropriate candidates.
The core implementation of
GetSymbolFilters is held in
GetCompletionFilters which returns an empty list by default. To add functionality, return an array of filters, for example:
These will create a filter that finds any enums, and returns
ResolveErrorType.NOT_RESOLVED if it can't find anything. If it finds an enum, it then goes through the
IsPublicFilter instance, which checks that the declared element is public. If it can't find a match, it returns
ResolveErrorType.ACCESS_RIGHTS. There are lots of implementations of
ISymbolFilter which can be sequenced together to filter down to the appropriate candidates.
The only other method that requires implementation (unless you wish to customise the class further) is
BindTo methods are implemented, and call into
BindToInternal. This method can use the
ReferenceWithinElementUtil.SetText method to set the text in the substring of the node, by creating a new token, with a passed in token factory
Func, using the new text of the node. It also fixes up any other references that are attached to the original node.
However, there is additional housekeeping required when the change is happening in a file that can contain multiple PSI trees (e.g. HTML files can contain HTML, JS and CSS trees). In this case you should use either the