This guide to the MPS editor actions contains several examples which are based on the MPS stricture and base language. To open the definition of these languages:
The language structure and the cell layout alone carry the information sufficient to equip the node editor with quite intelligent default behavior. As soon as the structure and the layout are specified the user can immediately create root nodes, insert children nodes, delete nodes, remove references, clean up property values (or reset to a default value), use auto-completion to choose or replace child or referent nodes, and so on.
Take, for example, the structure language. Here are snippets of the definition of the PropertyDeclaration concept and the ConceptDeclaration concept.
| structure | |
| editor |
The definition of the PropertyDeclaration concept tells that a property must be linked with some datatype. The name of associated datatype will be shown in the property editor after semicolon. Here we use the Ref.Cell cell to render the datatype.
| structure | ![]() |
| editor | ![]() |
The definition of the ConceptDeclaration concept specifies that a concept may contain zero, one or more properties (i.e. children of type PropertyDeclaration). These children are rendered as vertical list (it is the Ref.Node List cell here) under the caption "properties:".
Now lets create new concept and put focus to the 'properties' section.
Note: You can create new concept in any model which has the structure language among its languages. For more information about creating new projects/solutions/models and about assigning languages to the models read the "Getting Started with MPS" guide.
Press Insert (or Ctrl-Enter) to add new property, select its <datatype> cell and press Ctrl-Space to invoke the auto-completion menu, choose any option in the menu.

The default handlers for all these actions have been generated by MPS automatically basing on the information coded in the definition (structure and cell layout) of the concepts.
The auto-completion menu above is the example of the so-called "referent substitute menu" (the property declaration contains the reference to datatype).
The default builder of the referent substitute menu works as follows: it takes the current model and all its imported models as the search scope and collects all nodes which are instances of the target concept or its successors. Then it shows all the nodes found in the auto-completion menu.
The other type of auto-completion menu called "node substitute menu" and used to choose appropriate child node (we don't have example of such menu here).
The default builder of the node substitute menu works different. Instead of instances it seeks for concept declarations that can be instantiated in given context (substitute actions always work in context of so-called source node). The search scope is different as well: it includes all the languages associated with the model.
The default node substitute mechanism collects all concept declarations assignable the the target concept (i.e. concept of child node), excluding ones tagged as 'abstract' or 'doNotSubstituteByDefault'. Then, for each found concept it creates node substitute action which, when selected, will create instance of the concept and set it as child to the source node.
Note: Not all meta-cells in the cell layout carry the information required to generate the default action handlers. This discussion is only applicable to following types of the meta-cell: Ref.Node Cell, Ref.Cell Cell and Ref.Node List Cell.
Customizing of the auto-completion menu for child node is most common case in editors development in MPS.
Consider the Type concept in the baseLanguage. It is 'abstract' concept which has number of successors in the base language (primitives such as 'int' and 'boolean', ClassifierType and others) and in other languages (such as SNodeType in smodelLanguage).
Tip: To see all successors of the Type concept select it in project tree and choose item "Show Concept In Hierarchy" in the popup menu.
Nodes of type Type are widely used as children throughout the baseLanguage and in other languages. What we need is to get suitable and uniform auto-completion menu allowing us to choose the type wherever it is required.
For beginning, lets create new field declaration and invoke auto-completion menu on the <type> cell.

Apart from primitive types there is also the TheSampleClass in the menu. Choose this class and take a look at the inspector: new instance of the ClassifierType is created and added to the field declaration (type is child of field declaration) and the TheSampleClass has been linked with this instance of ClassifierType.
This couldn't be achieved with the default node substitute actions alone. Now take a look at the actions model in the baseLanguage. There is a root node named NodeSubstituteActions. Lets explore it in editor. Notice the record that concerns the Type concept.

This record declares that the custom actions factory named "Type" (rightmost cell) is assigned to build the auto-completion menu wherever the instance of the Type concept is needed. The name "Type" is, in fact, the name of query method.
Note: At the time of this writing all customizing in actions model is made using so-called query method technique. The query methods are regular Java static methods grouped in classes named Queries. Query methods normally invoked at a run-time by reflection. In future we are going to get rid of the query methods and replace them with embedded code-blocks that can be edited directly in the MPS editor. For more information about this topic see Using Action Maps
The following pseudo-code explains how this actions factory is implemented.
// create default Type substitute actions
// so we've got primitive types in the auto-completion menu
List<INodeSubstituteAction> actions = ModelActions.createPrimaryChildSubstituteActions(...);
// find all classifiers (i.e. classes, interfaces and others)
// available in context
ISearchScope searchScope = BaseLanguageSearchUtil.createVisibleClassifiersScope(model, IClassifiersSearchScope.CLASSIFFIER, ...);
List<SNode> classifiers = searchScope.getNodes();
// now for each classifier in the list create action
// we only need to override the createChildNode method
// in the DefaultChildNodeSubstituteAction class
INodeSubstituteAction action = new DefaultChildNodeSubstituteAction(...) {
public SNode createChildNode(SNode parameterNode, SModel model, String pattern) {
// create type for classifier
Classifier classifier = (Classifier) parameterNode;
ClassifierType classifierType = new ClassifierType(model)
classifierType.setClassifier(classifier)
return classifierType;
}
};
// finally, add new action to list
// and return result
actions.add(action);
return actions;
That's it. Now, it addition to primitive types we have classes and interfaces in our auto-completion menu.
As it is discussed earlier (see default referent substitute menu) the default auto-completion menu for referent nodes is made of all nodes of appropriate type found in the default search scope. I.e. in scope which includes all the nodes from the current model and from the imported models.
However, often the language semantic imposes additional restrictions on which nodes can be chosen as a legal referent.
For example, consider the FieldReference concept in the baseLanguage. Instance of this concept may look as shown on the screenshot.

Tip: The Ctrl-Shift-F7 selects a variable declaration alone with its usages. Pressing <Esc> will remove the selection.
The definition of the FieldReference is only telling us that an instance of this concept must have exactly one reference to an instance of the FieldDeclaration concept. In other words, a "field reference" node containing no reference to a field declaration is not valid.
But we also know that only fields declared in 'this' class are valid referents. This is the additional restriction which can't be expressed using the structureLanguage.
Fortunately we can work around this problem using the language's actions model.
Lets explore the root node ReferentSubstituteActions in the actions model of the baseLanguage.

This record declares that for node of type FieldReference (or of any descendant type) the search scope with codename "FieldReference_ClassifierHierarchy_InstanceFields" is applied to choose referent for role "fieldDeclaration".
As in previous chapter the "FieldReference_ClassifierHierarchy_InstanceFields" is the name of query method. Here is how this method is implemented:
// compute type of 'instance' ('this' expression in our case)
Type instanceType = JavaModelUtil.getTypeOfExpression(fieldReference.getInstance());
// we expect classifier type
ClassifierType classifierType = (ClassifierType)instanceType;
// create class hierarchy search scope
ISearchScope hierarchyScope = BaseLanguageSearchUtil.createClassifierHierarchyScope(classifierType, IClassifiersSearchScope.INSTANCE_FIELD,...);
// get collection of field declarations and wrap it into SimpleScope
return new SimpleSearchScope(BaseLanguageSearchUtil.getFieldsExcludingOverridden(hierarchyScope));
Tip: To see implementation of a query method press <Ctrl-B>.
The IntelliJ IDEA should be running and source files from the queries-src.zip in the MPS installation should have been unzipped to the source root of the opened IntelliJ IDEA project.
We don't need to define custom actions factory here because the default actions will work just fine (indeed, they know source node and referent role - that's all what needed to create the reference link).
Take a look at the cell layout of the BinaryOperation concept in the baseLanguage.

The cell responsible for rendering of the operation symbol is neither ref.node nor ref.cell cell (it is the 'concept property' cell) but, when writing programs in the baseLanguage we still want to be able to replace '+' with '-' and so on.
In this case the actions factory is assigned directly to the cell using the "auto-completion" property in the inspector for this cell.

Again, the inspector shows name of the query method. To explore the Java implementation of a query method press Ctrl-B.
To understand what the right-transform is and how it works lets return to boolean field (member of the TheSampleClass class that has been created earlier in course of this guide), select the <type> cell and set caret to the rightmost position, then press <Space> - the hint cell will appear. Now invoke the auto-completion menu - there is one option which will transform our simple boolean type to array of booleans.

If we apply right-transform to a class type, then there are more options in the menu:

Tip: If, when typing, the ritght-transform hint cell had appeared at undesirable moment, press <Esc> (or <Space> or <Delete>) to remove the hint cell and continue typing.
The right-transform hint cell doesn't appear any time after pressing of <Space>. That's because some nodes are associated with transformation actions while other nodes aren't.
To see what transformation actions are associated with the <type> node lets go to the actions model in the baseLanguage and explore the root node RTransformHintSubstituteActions.

There are two records concerning the Type concept. The first record specifies simple menu applicable for any node of type Type (or of any descendant type). With this menu we can transform any type to array type. The second record specifies the additional menu which is only applicable to the ClassifierType. This addition allows to add type parameters to a classifier type as it is shown in the example above.
Tip: To see implementation of a query method press <Ctrl-B>.
The IntelliJ IDEA should be running and source files from the queries-src.zip in the MPS installation should have been unzipped to the source root of the opened IntelliJ IDEA project.
By default the right-transform hint cell is inserted after rightmost cell of the node. However, in some rare cases the hint cell needs to appear at the side of the particular cell (not rightmost).
To see the case open the declaration of our TheSampleClass class, set caret to the end of the class's name and press <Space>.

The hint cell is inserted just after the <name> cell. Why? Let's explore the cell layout defined for the ClassConcept:

That's it - the name cell is tagged as the DEFAULT right-transform anchor in the cell's inspector.
It is also possible to specify extra (up to 5) right-transforms using tags EXT_1, EXT_2 etc. The NewExpression concept is an example. The NewExpression concept inherits from the Expression concept. Hence it inherits all transformation actions accosiated with an expression. This set of actions is available at the 'closing bracket' (as the 'closing bracket' is rightmost cell in 'new expression' node).

But, in addition we want to be able to add type parameters right after the constructor's name as it is shown below.

To handle this rather exotic case the cell responsible for rendering of the constructor name and the transformation actions are both tagged with the EXT_1 tag.

The action maps allow to completely redefine default handling of the DELETE and RIGHT-TRANSFORM actions in MPS editor.
For instance, press Ctrl-Delete on a field reference - the field reference expression is replaced with expression before the period.

This is custom behavior (by default the DELETE action would just remove reference to field declaration) .
The custom handler which handles deleting in the field reference node is specified in the editor model of the baseLanguage.

This action map is assigned to the cell responsible for rendering of the field name.

Note: to specify the action handler we use embedded code-block and the smodel DSL which has been designed specifically for manipulation with nodes in MPS models. In the example above, single expression written in the smodel language is equivalent to several statements of base language. The embedded code-blocks still allow to write implementation code in plain base language and, since the smodel language is extension of the base language the expressions from both languages can be blended as needed.
The smodel DSL is still under development. At the time of this writing it doesn't implement all operations needed to perform any arbitrary transformation of a model nodes. To get an idea of what operations are implemented in the smodel DSL open the MPS project at:
MPS_HOME\languages\bootstrap\smodelLanguage\smodelLanguage.mpr
You will find the 'sandbox' solution in this project. The 'AClass' class inside this solution presents many examples of expressions written in the smodel DSL.
The 'testInputLanguage' defined in the same project serves as the input language for these expressions.
Fill free to play in the sandbox. You can also generate Java code from the 'AClass' class.
The keymaps allow to customize handling of specific keystrokes in the MPS editor.
For instance, there is a keymap in the baseLanguage which specifies handler for Ctrl-Shift-P keystroke.

The Ctrl-Shift-P when pressed while the focus is on an expression, will put the expression in parenthesis.
