In MPS, any model consists of nodes. Nodes can have many types of relations. These relations may be expressed in a node structure (e.g. "class descendants" relation on classes) or not (e.g. "overriding method" relation on methods). Find Usages is a tool to display some specifically related nodes for a given node.
In MPS, the Find Usages system is fully customizable - you can write your own entities, so-called finders, which represent algorithms for finding related nodes. For every type of relation there is a corresponding finder.
This is how "find usages" result looks like:
Using Find Usages Subsystem
You can press Alt+F7 on a node (no matter where - in the editor or in the project tree) to see what kind of usages MPS can search for.
You can also right-click a node and select "Find Usages" to open the "Find usages" window.
The Find usages window adopts to the node under caret. For behavior methods, for example, it gives the users to select "Overridden Methods" and "Overriding Methods".
Finders - select the categories of usages you want to search for
Scope - this lets you select where you want to search for usages - in concrete model, module, current project or everywhere.
View Options - additional view options
After adjusting your search, click OK to run it. Results will be shown in the Find Usages Tool as shown above.
To implement your own mechanism for finding related nodes, you should become familiar with Finders. For every relation there is a specific Finder that provides all the information about the search process.
Where to store my finders?
Finders can be created in any model by importing findUsages language. However, MPS collects finders only from findUsages language aspects. So, if you want your finder to be used by the MPS Find Usages subsystem, it must be stored in the findUsages aspect of your language.
The name of a finder. You can choose any name you want, the only obvious constraint being that the names must be unique in the scope of the model.
Finder will be tested for applicability only to those nodes that are instances of this concept and its subconcepts.
This string represents the finder in the list of finders. Should be rather short.
If it's not clear from the description string what exactly the finder does, you can add a long description, which will be shown as a tooltip for the finder in the list of finders.
Determines whether the finder is visible for the current node. For example, a finder that finds ancestor classes of some class should not be visible when this class has no parent.
Finders that have passed for concept are tested for applicability to the current node. If this method returns true, the finder is shown in the list of available finders; otherwise it is not shown. The node argument of this method is guaranteed to be an instance of the concept specified in "for concept" or its subconcepts.
Note the difference between is visible and is applicable. The first one is responsible only for viewing. The second one represents a "valid call" contract between the finder and its caller. This is important because we have an execute statement in findUsagesLanguage, which will be described later. For more information, refer to the following execute section.
This method should find given node usages in a given scope. For each found usage of a node, use the add result statement to register it. For data other than nodes that the Find Usages dialog is able to visualize use add raw result.
This method returns nodes for which the finder searched. These nodes are shown in searched nodes subtree in the tool.
For each node to display, use the add node statement to register it.
There are a number of variants to group found nodes in the tool. One of them is grouping by category, that is given for every found node by the finder that has found it. This method gives a category to each node found by this finder.
What does the MPS Find Usages subsystem do automatically?
Stores search options between multiple invocations and between MPS runs
Stores search results between MPS runs
Automatically handles deleted nodes
All the visualization and operations with found nodes is done by the subsystem, not by finders
Finders can be reused thanks to the execute statement. The execution of this statement consists of 2 steps: validating the search query (checking for concept and isApplicable), and executing the find method. That's where you can see the difference between isApplicable and isShown. If you use isApplicable for cases when the finder should be applicable, but not shown, you can get an error when using this finder in the execute statement.
Furthermore, in order to execute a finder asynchronously one needs to use a special OnEachNodeFoundByExpression, which represents a simple finder invocation with a callback which is executed for each found node.
You can see some finder examples in jetbrains.mps.baseLanguage.findUsages
You can also find all finders by going to the FinderDeclaration concept (Ctrl+N, type "FinderDeclaration", then press ENTER) and finding all instances of this concept (Alt+F7, check instances, then check Global Scope).