Transformation Menu Language
Transformation menu language is used to define transformation menus that describe a hierarchical structure of submenus and actions that will appear in various locations in the editor. Currently there are several possible locations where transformation menus are shown: side transform menus, substitute menus, context assistant, and context actions tool. Language designers and plugin authors can define additional locations and specify required or optional features for each location (such as an icon or a tooltip), as documented in Extending the Transformation Menu Language.
Understanding the menus
Substitute menus offer options to replace the current node with another one. The offered nodes must be compatible with the position into which the node is eventually going to be inserted. Thus, when the substitute menu definitions are looked up, the input to the lookup algorithm is not the concept of the existing node but rather its containment link. It's the "place" in the AST that is important, not the node that is currently occupying the place.
Consider as an example an instance of ExpressionStatement concept from BaseLanguage, which has an 'expression' child role expecting a concept of Expression. Let's assume it is holding an AssignmentExpression (`a = 5;`). If the user wants to replace (substitute) the AssignmentExpression inside of ExpressionStatement with a different node, any Expression will work, because that's what the target concept of the ExpressionStatement.expression containment link is. The current node is irrelevant for the purpose of finding the relevant substitute menu definitions.
Most concepts will implicitly use a substitute menu definition that contains:
An action for creating a node of this concept, unless it's abstract
An instruction to include the default menu definition of all subconcepts of this concept
As a result, when building a menu for Expression, MPS will traverse its subconcepts and include them in the menu as well. But note that superconcepts are not considered. A completion menu for ExpressionStatement.expression will not contain entries from the substitute menu definition of BaseConcept because an instance of BaseConcept can't be substituted there, only an Expression can.
Transformation menu definitions compose in the opposite way from substitute menu definitions. Since the idea is that we want to transform an existing node, we look at its concept and go look up the transformation menu for it. If no menu is defined for that praticular concept MPS will fall back to the default menus for the superconcepts (since an instance of a concept can also be considered instance of any of its superconcepts per Liskov substitution principle). Note that if you write a default transformation menu definition then the fallback behavior does not apply and you need to specify it explicitly (using the `superconcepts menu` instruction). If you look at the default transformation menu for BaseConcept, called BaseConcept_TransformationMenu, you'll see that it includes the default substitute menu for the current link.
It's not possible to attach substitute menu definitions directly to editor cells, you have to include them in a transformation menu first. The default substitute menu definition is already included in the default transformation menu definition for BaseConcept so you might not need to do this if you only work with default menu definitions. The completion menu is built from transformation menu definitions. If a definition says "include this substitute menu definition" then it gets included, otherwise it doesn't.
Defining a Menu
Transformation menus define UI actions that will be shown in various locations. At design time a menu is specified as a list of sections, each section contains a list of menu parts for a particular set of locations. At runtime the menu parts and the locations are used to generate the contents of the menus (menu items).
Menu definitions come in two flavors: default and named. Menu definitions can also be extended through menu contributions.
Each concept has a default transformation menu associated. If the language designer does not provide one explicitly, a transformation menu defined for the closest super-concept is assumed. If none is specified for any of the super-concepts, the one defined on BaseConcept is used, which contains the substitute actions suitable for that position (see below the section on substitute actions).
A default menu is used in situations where the language designer hasn't specified which menu to display.
A named menu is an additional menu for a concept. Like the default menu it also specifies an applicable concept and contains a list of sections. As the term suggests, a named menu has an explicitly set name. A named menu is meant to be set as the transformation menu of a cell or included into another menu via the Include Menu menu part.
Attaching a named menu to an editor cell:
Note: Default transformation menus can also be attached to individual cells the same way named menus can.
A menu contribution extends a given menu by contributing additional menu parts to it. This is in particular useful, when an extending language needs to add entries into a menu defined in the extended language. Contributions can actually only be defined in languages other than the one with the menu being contributed to.
When a menu is requested at runtime the original definition and all contributions are merged and the menu is created using the combined definition. A few important notes on contributions:
The order, in which the individual definitions are merged is currently unspecified.
A contribution cannot remove menu parts from the menu it contributes to.
It is possible to define a contribution to the implicit default menu for a concept.
By specifying location for a section within the menu you indicate, into which part of the UI the actions should be inserted:
completion - the completion menu
context actions tool - the Context Actions Tool (requires import of jetbrains.mps.editor.contextActionsTool.lang.menus language)
context assistant - the Context Assistant
side transform - left or right transformations
The following standard menu parts are available:
action – a simple menu item specifying an action to be performed, its corresponding menu text and applicability.
group - a collection of menu items. Beyond these items a group may define one or more variables that are then shared by all actions within the group to avoid repetitive computation. Additionally, a group holds a condition that indicates when the actions in the group should be made available to the user.
include - include a specific default or named menu (together with its contributions, if any). Inclusion cycles are detected at runtime and an error message is produced.
include substitute menu - include a default or named substitute menu to use as part of this menu.
parametrized - an action that is parametrized with multiple values.
submenu – a submenu containing further parts.
superconcepts menu – includes the default menus of the superconcepts of the applicable concept since these are not included by default.
wrap substitute menu - wraps a specified concept using the provided handler
jetbrains.mps.lang.editor.menus.extras contains adapters to include various action-like entities from transformation menus:
intention – wraps an intention (a subconcept of
refactoring – wraps a refactoring
plugin Action – wraps a plugin action
When you edit code in a text editor, you can type it either from left to right:
or from right to left
In order to emulate this behavior, MPS has side transform actions: left and right transforms. They allow you to create actions which will be available when you type on left or right part of your cell. For example, in MPS you can do the following:
or the following:
The first case is called right transform. The second case is called left transform.
You define side transformations in the Transformation menus by choosing the side transform location for the section:
The language also enables language designers to include items of the side-transform menu to the completion menu on a specific cell. To do this you attach to the desired cell a transformation menu that contains a completion section. That completion section will hold an include menu part and specify the location of side transform. The items of side-transform menu will be included to the completion.
The location of the included menu can be specified using the intention "Specify Location".
Substitute actions define user-invoked transformations to some parts of the model, during which one node is substituted by another node. The actually mapping of these substitute actions to the visual UI elements (completion menu, etc.) is then done through Transform menus (see the section above).
Typically substitute actions are triggered by pressing Ctrl + Space in the editor. The completion menu that shows up contains options that, when selected by the user, will replace the node under caret. Unlike side-transformations, context assistant or context action tool, substitutions have default behavior, which takes effect, unless the language author defines otherwise.
Default behavior for code-completion
Without any menus implemented explicitly by the language author, MPS will still provide a completion menu with substitutions for the current node in either of these two cases:
The cursor is positioned at the front of a single-cell editor
The user has selected the whole editor of a node
In these cases pressing Control + Space will show a menu with all concepts from the imported languages applicable in the given context, which can substitute the current node in the model.
MPS follows these steps to populate the default completion menu:
If your selection is inside of a position which allows concept A, then all enabled subconcepts of A will be available in the completion menu.
All abstract concepts are excluded
All concepts, for which the 'can be a child' constraint returns false, are excluded
All concepts, for which the 'can be a parent' constraint of a parent node returns false, are excluded
If a concept contains a 1:1 reference, then it is not added to the completion menu itself. Instead, an item is added for each element in scope for that reference. We use a name smart reference for such items.
To customize node substitutions, the substitute menus are used.
Substitute menu (default)
By defining a default substitute menu for a concept you may customize the contents of the completion menu, which displays when the user presses Control + Space. It also has effect on sub-concepts of the concept, unless these sub-concepts define their own default substitute menus.
However, if you want to assign the substitute menu to a particular cell of an editor, you will need to include your substitute menu in a transformation menu, because only transformation menus can be attached to editor cells.
Substitute menu (named)
Named substitute menus give you the flexibility to create multiple substitute menus and use them in different contexts. Named substitute menus must be first included in another substitute menus or transformation menus to take effect.
Substitute menu contribution
Just like with Transform menus, Substitute menu contributions contribute new entries into substitute menus defined in an extended language.
add concept - adds a single concept to the menu
concept list - adds a collection of concepts
group - adds a groups of entries, if a condition is met
include - includes a specified menu
parameterized - adds a parametrized substitute action
reference actions - includes and customises the appearance of the possible targets of a reference
subconcepts menu - includes all subconcepts of the concept
substitute action - adds a single substitute action
wrap substitute menu - wraps a specified concept using the provided handler
Interaction of Cell Menu and Cell-specific Transformation Menu
If a cell has both "menu" and "transformation menu" specified the applicable entries from both menus are combined. Some cell menu parts (descendants of
CellMenuPart_Abstract such as
CellMenuPart_PropertyPostfixHints) do not yet have an equivalent transformation/substitution menu part.
Include transformation menu for the property/reference
The customization of the menu for the property/reference cells can be done with the property/reference transformation menu parts. Suppose you want to customize the completion menu of the reference/property cell. Previously, it could have been done by defining the "inline menu" in the inspector. Now it can be done also via the Transformation Menu. We have introduced the property and the reference transformation menu parts. The reference transformation menu part includes the actions, which set the target node to the specific reference. The target nodes come from the scope for that reference. The same goes for the property menu part: it takes the values of the property type and includes actions, which set the value to the specific properties.
Example on references
Suppose we want to build the completion menu for a reference cell. The menu should contain the usual targets of the reference that are in scope, plus some other custom action. That could be done by attaching the following named transformation menu to the reference cell:
So we will receive all the standard reference actions as well as our custom action in the completion:
As you can see the reference menu part is the analog of the "primary choose referent menu" part in the "inline menu", but more customizable. Also, if no menu is attached to a cell, its reference menu will be used by default.
Example on properties
One more example - suppose you want to see all the possible values for a specific property in the context assistant when the cursor is on a label cell next to that property cell. Let's take a look at a piece of code in the Kaja language:
We want to see all the variants of the looking direction when we put the caret to the looking cell. So we attach the following menu to this label cell:
And get the result:
The advantages of these menu parts over the "inline menu" include:
They can be attached to any cell, not just reference/property cells.
They can be used in any menu location (context assistant, context menu), not only the completion.
The reference menu is more customizable (the property menu will also be customizable soon).
The menu discovery algorithm
Understanding the process of how MPS picks the transformation menu will help you design menus with more confidence.
The built-in behavior for discovery of transformation menus is to include the menu(s) of superconcept of the current concept, so by default MPS will look for a transform for the current concept, named <CurrentConcept>_TransformationMenu, then its super-concept's menu and so on, until BaseConcept_TransformationMenu.
Substitute menus are similar to transformation menus, but their discovery works in the opposite direction: where the transformation menu for a concept A includes the menus of its superconcepts (up to BaseConcept), i.e. walks up the hierarchy, the substitute menu for A includes menus for its subconcepts, because only the sub-concepts can safely replace A in the model. Note that substitute menus are a bit different from transformation menus, because they are looked up not based on the concept of an existing node, but instead based on the link's target concept.
Show Item Trace
To track, which transformation or substitute menu contributed a particular action to the completion menu or to the context assistant, users just press Control/Cmd + Alt + B on the completion menu entry and an interactive trace report will show up.
Sometimes it is hard to track how the action appeared in the completion or context assistant because of there are many substitute and transformation menus including each other. Now you may select some action in completion (by arrows) or in the context assistant (by pressing cmd/ctrl+alt+Enter) and then press cmd/ctrl+alt+B. You will see the trace in the project tool. This is the trace of the menu and menu part declaration which include each other starting from the top-level menu and ending with the action declaration. If the menu or the menu part declaration is explicit and is in the project, then it has bold style in tool and you can click on it and go to the declaration.
Here's how it looks like when we put the caret at a statement, show completion for the variable reference and then invoke "Show Item Trace":
We see that what we see in completion is the default transformation menu for the Statement which includes the menu for superconcept which is the BaseConcept. It in its turn includes the substitute menu for the Statement, which in it's turn wraps the menu for the Expression. Then it comes to subconcepts of Expression, one of which is the VariableReference. VariableReference is the "smart reference" concept, so it tries to find all visible target of the Variable concept. So that's how the variable reference appears in the menu for the statement.