MPS 2020.3 Help

Editor cookbook

This document, intended for advanced language designers, should give you the answers to the most common questions related to the MPS editor. You may also like to read the Editor documentation, which contains exhaustive information on the subject.

How to create an editor definition

The DSL that MPS offers language designers to design editors is built around the notion of cells. The language designer combines editor cells and places them on the screen in a way that reflects the desired final layout of the notation.

Sni mek obrazovky 2018 10 17 v 17 45 11

The inspector window at the bottom of the screen, which can be slides in after pressing the  Inspector tool button in the bottom-right corner of the screen or using the  Alt/Cmd + 2 keyboard shortcut, shows additional styling and layout properties of the cell selected in the editor above.

An empty editor definition shows a red placeholder for a single cell

Sni mek obrazovky 2018 10 17 v 14 55 02

Pressing  Control + Space will offer various types of editor cells that can be inserted. The  constant cell, for example, will display given un-modifiable text label.

Sni mek obrazovky 2018 10 17 v 14 59 55

An empty constant text looks like this in the editor definition:

S0

When used by the end user, the editor will show an empty space in place of the empty constant cell.

You can type any arbitrary text that you want the constant cell to display to the user at run-time:

Sni mek obrazovky 2018 10 17 v 15 00 16

Cells mapped to properties, nodes or references

The completion menu also offers cells that will show values of a node's property, a child or the target of a reference. These cells also typically allow the users to modify the values displayed.

Sni mek obrazovky 2018 10 17 v 14 55 16

Sni mek obrazovky 2018 10 17 v 14 55 50

Layout

Editors that consist of a single cell are not very useful. In order to combine multiple cells in the editor definition a  collection layout cell must be used. MPS offers three types of these -  vertical, horizontal and  indent collection layout cells.

Sni mek obrazovky 2018 10 17 v 14 56 12

The collection layout cell will take care of positioning the cells that it wraps:

Sni mek obrazovky 2018 10 17 v 14 56 26
Sni mek obrazovky 2018 10 17 v 14 56 51

Hit  Enter to insert and additional cell:

Sni mek obrazovky 2018 10 17 v 14 57 01
Sni mek obrazovky 2018 10 17 v 14 57 15

This way you can insert as many cells as needed. Nesting  collection layout cells in other  collection layout cells is possible.

The  indent layout gives the designer control over where to wrap lines. Setting the  indent-layout-new-line property in the inspector for a cell will make the cell the last on a line.

S1

The following cell will be placed on the line below:

S2

Similarly, setting the  indent-layout-indent  property will make a cell indented:

S3

How to properly indent a block of code

Nested blocks of code are typically represented using indented collections of vertically organized statements. In MPS you almost exclusively use indented layout  for such collections, if you aim at making the experience as text-like as possible. Vertical  and  horizontal  layouts should be only considered for positional or more graphical-like layouts. 

So for properly indented code blocks you need to create an Indented collection  ([- ... -]) and set:

  • indent-layout-new-line-children  so that each collection element (child) is placed on a new line

  • indent-layout-indent  to make the whole collection indented

  • indent-layout-new-line  to place a new line mark behind the collection, so that next nodes get placed on a new line underneath the collection

  • indent-layout-on-new-line  Optionally you may need this flag to place the whole collection on a new line instead of just appending it to the previous cell

Editor15

Enhancing the editor definition

Alias

Concepts can have an alias  defined, which will represent them in code-completion pop-up menu and which will allow users to insert instances of these concept just by typing the alias. The editor  can then refer to the text of the alias using the AliasEditorComponent  editor component. This is in particular useful, when multiple concrete concepts inherit an editor from an abstract parent and the alias is used to visually differentiate between the multiple concepts.

Editor101

Editor102

Editor103

You then refer to the alias value through the conceptAlias  property:

Editor104

Unique constraint

In general, constraints allow you to restrict allowed nodes in roles and values for properties and report violations to the user.

Editor41
Editor43

Frequently you'd like to apply constraints to limit allowed values in your code. E.g. the names of method definitions should be unique, each library should be imported only once, etc. MPS gives you two options:

  1. Use constraints  - these define hard validity rules for abstract syntax (structure) of your language. Use these if you want to disallow certain nodes or properties to be ever made part of the AST.

  2. Use NonTypesystemRules  - these provide additional level of model verification. You are still allowed to enter incorrect values in the editor, but you will get an error message and a red underline that notifies you about the rule violation. Unlike with constraints  you can also specify a custom error message with additional details to give the developer helpful hints.

Constraints

Editor42

NonTypesystemRules
Editor44

An indented vertical collection

Indent layout is the preferred choice instead of vertical/horizontal ones where applicable. To create an indented vertical collection you can create a wrapping indent collection (marked with [-  -]) and set

  1. indent-layout-indent: true

  2. selectable : false. (false means the collection will be transparent when expanding code selection).

Then, on the child collection use indent-layout-new-line-children : true.

Optional visibility

Elements, that should only be visible under certain condition, should have its show if  property set:

Editor16

Defining and reusing styles

Each cell can have its visual style defined in the Inspector. In addition to defining style properties individually, styles can be pre-defined and then reused by multiple languages.

Editor37
Editor38

You can define your own styles and make them part of your editor module so that they can be used in your editors as well as in all the editors that import your editor module.

Editor39

Reusing the BaseLanguage styles

BaseLanguage comes with a rich collection of pre-defined styles. All you need to do in order to be able to use the styles defined in another language is to import the language into the editor of your language.

Editor40

Making proper keywords

Keywords should have the keyword  style applied so that they stand out in text. Also, making a keyword editable  will make sure users can freely type inside or next to the keyword and have transforms applied. With editable  set to false  MPS will interfere with user's input and ignore characters, which don't match any applicable transformation.

Editor18

Adjust abstract syntax for easy editing

Making all concepts descent from the same (abstract) concept allows you to mix and match instances of these concepts in any order inside their container and so give the users a very text-like experience. Additionally, if you include concepts for empty lines and (line) comments, you will give your users the freedom to place comments and empty lines anywhere they feel fit.

Editor2

In our example, the SStructureContainer  does not impose any order, in which the members should be placed. The allowed elements all descend from SStructurePart  and so are all allowed children.

Specify, which concept should be the default for lists

When you hit Enter  in a text editor, you get a new line. Identically, you may want to an  empty line concept  or some other reasonable default concept of your language to be added to the list at the current cursor position, when the user presses  Enter.

The behavior in the second image below should be preferred to the one visible on the first one:

Editor5

Editor6

The red placeholder is caused by either a missing node in the position or an instance of an  abstract concept. Since abstract concepts should not be instantiated, the editor always shows them in red.

When the user hits Enter on a collection cell a new node is created and inserted into the collection. The concept of the newly created and inserted node is the same as the declared concept of the collection. If the concept is  abstract, the newly created and inserted node will be in instance of that abstract concept and thus rendered in red.

There are three ways to prevent such situations:

  1. Use a non-abstract concept to declare the concept of the child collection.  BaseLanguage uses this approach for  Statement, which is a common super-concept to all statements and is not abstract in order to represent empty lines.

  2. Specify a  default concrete concept in the  Constraints of the abstract concept. This will use the specified concrete concept whenever the abstract one is to be instantiated. Such a concrete concept will then be used an a concept for empty lines.

  3. Use  element factory on all collections, in which a different than the abstract concept should be used for creating newly inserted nodes (described below).

Specifying default concept is as easy as setting the element factory  property of the collection editor:

Editor3

Make empty lines helpful to further editing

Making empty lines to appear as default after hitting Enter  is the first important step to mimic the behavior of text-editors. The next step that will get you even closer is to make empty lines react reasonably to user input and provide handy code completion.

Editor25

You should make your empty lines similar to the one above - add an empty  constant  cell, make it editable  and specify the super-type of all items to populate the code-completion menu with. If you followed the earlier advice and have all your concepts, which could take the position of the empty line, descend from a common ancestor, this is the type to benefit. Specify that ancestor as the concept to potentially replace empty lines with.

Hiding concepts from the completion menu

The completion menu can be customized using the Transformation Menu language. By default it shows all non-abstract concepts that can be substituted in the current location. Some concepts, such as empty lines or comments, you may want to exclude from the completion menu explicitly. To do so, you need to define a  Default Substitute Menu for the concerned concept or its super-concept in the editor aspect of your language and leave it empty. MPS uses that menu to populate the completion menu with items whenever your concept is considered for substitution. Leaving the menu empty will result in your concept never getting inserted into the completion menu.

Easy node replacement

Similarly, you will frequently want to allow developers to replace one node with another in-place, just by typing the name of the new element:

Editor26

Just like with empty lines, for this to work, you set the replacement concept to the common ancestor of all the applicable candidate concepts. These will then appear in the code-completion menu.

Editor27

Vertical space separators

To create some vertical space in order to separate elements, constant  cells are very handy to utilize. Give them empty contents, set noAttraction  for focus to make it transparent and put it on a separate line with  indent-layout-new-line.

Editor4

Handling empty values in constants

A value of a property can may either hold a value or be empty. MPS gives you three knobs to tune the editor to react properly to empty values. Depending on the values of the allow-empty, text*  and empty text*  flags, the editor cell may or may not turn red or display a custom message when the property is empty.

Empty values not allowed.
The cell is displayed in red to indicate an error.

Editor19a

Editor19b

Empty values not allowed.
A custom message has been provided to empty cells.

Editor24a

Editor24b

Empty values are allowed.
An empty cell displays a default message in gray color.

Editor20a

Editor20b

Empty values are allowed.
A custom message has been provided to empty cells.

Editor21a

Editor21b

Empty values are allowed.
The empty cell is visually transparent.

Editor22a

Editor22b

Empty values not allowed.
The empty value has no default text and is marked in red.

Editor23a

Editor23b

Horizontal list separator

The separator  property on collection cells allows you to pick a character that will

  1. Visually separate elements of the list

  2. Allow the user to append or insert new elements into the list

Editor7
Editor8

Although separators can be any string values, it is more convenient to keep them at one character length.

Matching braces and parentheses

Use the matching-label  property to pair braces and parentheses. This gives the users the ability to quickly visualize the wrapped block of code and its boundaries:

Editor9
Editor10

Since MPS 3.4 you can also use show-boundaries-in style on a collection cell to specify how its boundaries are to be shown. The style has two values, gutter and gutter-and-editor. If you set the style of a collection to gutter-and-editor, the collection's first and last cell will be highlighted in the editor when one of the cells is selected and a bracket will be shown in the left-hand editor gutter. Setting the style to  gutter will only show the bracket and may be useful if you want to show where a particular collection begins and ends but there isn't a well-defined last cell in the collection.

Example:

Image2016 7 26 11 7 5

The result:

Image2016 7 26 11 9 21

Empty blocks should look empty

By default an empty block always takes one line of the vertical real-estate. It also contains a default empty cell, which gives the developer a hint that there's a list she can add elements to.

Editor14

You may hide the << ... >>  characters by setting the empty cell  value to be an empty constant cell, which gives you a slightly more text-like look:

Editor11

One additional trick will hide the empty line altogether:

Editor12

What you need to do is to conditionally alter the indent-layout-new-line  and the punctuation-right  properties of the opening brace to add/remove a new line after the brace and assign control of the caret position right after the brace to the following cell. Since the empty constant cell  for the members  collection follows and is editable, it will receive all keyboard input at the position right after the brace. This will allow the developer to type without starting a new empty line first.

Editor fix3

Make empty constants editable

It is advisable to represent empty cells for collections with empty constant values that have the editable  property set to true. This way users will be able to start typing without first creating a new collection element (via Enter  or the separator key ).

Editor17

Common editor patterns

Prepending flag keywords

Marker keywords prepending the actual concept, such as final, abstract  or public  in Java, are quite common in programming languages.

private abstract class MyCalculator { ... }

Developers have certain expectations of how these can be added, modified or deleted from code and projectional editors should follow some rules to achieve pleasant intuitiveness and convenience levels.

  • Typing part of the keyword anywhere to the left of the main concept name should insert the keyword

  • Hitting delete  while positioned on the keyword should remove it

  • The keyword should only be visible when the associated flag is true  - for example, the final  keyword is only shown for final  classes in Java

Notice that the keywords are optional, with the show if  condition querying the underlying abstract model.

Flags1

The action map  property refers to an action map, which specifies that when the DELETE  action is invoked (perhaps by pressing the delete  key), the underlying abstract model should be updated, which will in turn make the flag keyword disappear from the screen.

Flags2

Notice that the  aproveDelete call gives the user a chance to revoke her decision to delete the flag and also how positioning the  cursor after deleting the flag is handled.

To allow the developers to add the keyword just by typing it, we need to define a left transform action, which, in our example, when applied to a non-final element will make the element final after typing "final" or any unique prefix of it. The  Transformation Menu language gives us  Transformation Menus, which we can utilize here:

Flags3

Named transformation menu can then be attached to the editor cells that precede or succeed our flag cell:

Flags4

Appending parametrized keywords

Supporting keywords similar to Java's implements  is also very straightforward in MPS.

Editor30

First, the whole implements A, B C  part must be optionally visible.

Editor36

Only when the list of implemented interfaces is non-empty, the collection including the implements  keyword is displayed.

Second, we need a right transformation to add a new child into the implements  collection, when implements  or a part of it is typed right after the class name or after the reference to the extended  class. Similarly, a left transformation is needed to add a new child when implements  is typed right before the code block's left brace. Again, the  Transformation Menu language will let us create named transformation menus and attach them to the cells.

Node substitution actions

Node substitution actions  specify how certain nodes can be replaced with others. For example, you may want to change logical and  to or  and vice versa, yet preserve the boolean conditions specified in the child nodes:

Editor105

Let's use Default substitute menu  from Transformation Menu language:

Subst1


Since both And  and Or  concepts inherit from LogicalOperator, we can refer to LogicalOperator  in the action. In essence, the action above allows replacing any LogicalOperator  with any non-abstract  subconcept of LogicalOperator. The replacing concept is instantiated and its left  and right  children populated from the children of the node that is being replaced.

Substitution for custom string values

Substitution rules can also be used to convert plain strings into nodes of a desired concept. Imagine, for example, a kind of variable declaration that starts with the name of the variable, like in Python:

myVariable = 10;

To enter such variable declaration, you can always pick the variable concept (by its alias) from the completion menu and then fill in the name:

Var1

Var2

Var3

You can, however, add a simple substitution action into the substitution menu that will enable users to simply type the desired name of the variable on the empty line and have the variable created for you automatically:

Var5

Var6

The menu may look something like this:

Var7

  1. It can be substituted whenever a non-empty pattern has been typed and it does not match an alias of any of the known sub-concepts applicable in the position

  2. A new node is created when the action is run and the pattern is copied into the name of the variable

  3. The "selection handler" ensures that the cursor stays within the "name" cell after creating the variable so that the user can continue typing the name even after the variable has already been created (it is created as soon as there is no other option in the completion menu (the "selection handler" returns null, since when a non-null node is returned, MPS would set the cursor itself on the returned node and would ignore the "select" command)

You may also experiment with checking the "strictly" flag to further customize the behavior:

Var4

Here's the menu:

Var8

Changes:

  1. The description provides a customized description message to display in the completion menu alongside the "variable" alias

  2. The  pattern ensures the so far typed text is suggested as the name of the variable to be created (when the code-completion menu is visible)

  3. The  can substitute handler reacts to the value of the  strictly parameter, so that as long as there are other subconcepts available in the menu this action is also available, but only for non-strict matching.

 

Including parents transform actions

Your nodes can optionally include transform actions applicable to different nodes, e.g. parents. For example, if we allow for appending logical and  and or  to logical expressions, we may still get into problems when the logical expression is more complex and, for example, the last cell of its editor belongs to a child node.

Editor110

In our example, heading south  is a logical expression, however, south  itself is a child of logical expression with a concept Direction. Thus the original right transform action that accepts and  and or  to be appended to logical expression will not work here. We must include the original right transform (applicable to Heading ) action into a new right transform action applicable to Direction  specifically.

Inherit1

Conceptual questions

Extending an existing editor

The MPS editor assigns visual cells to nodes from the model and so delegates to the node's concept the responsibility for redering the coresponding values and accepting user input. This mechanism will work irrespective of the language the concepts has been defined in. So an embedded language such as, e.g. a math formula, will render itself correctly, no matter whether it is part of a Java program or an electrical circuit simulation model, for example.

New sub-concepts will by default re-use the editor of their parent concept, unless a specific editor is available. On the other hand extending languages may supply their own editors for inherited concepts and thus override the concrete syntax derived from the inherited editor.

Design an editor for extension

A good strategy is to use Editor components  to modularize the editor. This will allow language extensions to override the components without having to redefine the editor itself.

References to properties, such as conceptAlias, from within the editor should be preferred to hardcoded literals, since the reference will allow the editor to adapt to the subconcepts without having the override the editor.

When specifying actions' and intentions' applicability rules, bear in mind that some subconcepts may need to opt out from these actions of their parent. Making these actions check a behavior method in their applicability rules is advisable in such scenarios.

How to add a refactoring to the menu

Use InlineField  or IntroduceVariable  as good examples. In general, you need to define an Action  from the  jetbrains.mps.lang.plugin  language, which specifies its applicability, collects contextual information and the user input, initializes the actual refactoring procedure and invokes it. The refactoring functionality is typically extracted into a BaseLanguage  class and potentially reused by multiple actions.

How to define multiple editors for the same concept

A sample first

The MultipleProjections  sample project bundled with MPS provides good introductory guidelines to learn how to define multiple editors per concepts and how to allow switching between them.

Mp1

The sample languages allow you to define workflows, which consist of one or more state machines. State machines can be expressed either structurally or as tables. The programmer can switch between notations used for each state machine simply by typing either structural  or tabular  at the beginning of the corresponding state machine definition:

Mp2

The sample consists of three languages and a sandbox project.

Mp3

The requestTracking  language provides the concepts and root concepts to wrap state machines and use them to define simple workflows. This could serve as an example of language that needs to embed a state machine language and allow for alternative notations for that embedded language.

The stateMachine  language defines the basic concepts of the state machine language plus default editors. It has no artifacts specific to multiple projections at all. To illustrate the power of language extension in MPS the alternative editor projections for some of the concepts have been defined in stateMachine.tabular, which extends stateMachine.

Mp5
Mp4

While the default editors specified in  stateMachine  indicate the fact of being default with the  default  value in the upper-left corner, the editors in  stateMachine.tabular  specify the  tabular   hint. Specifying multiple hints for a single editor is also possible:

Mp6

Hints

The key element in choosing the right projection are Hints. Editors specify, which hints will trigger them to show up on the screen. Hints are defined  using the new ConceptEditorContextHints  concept.

Mp7

This concept lets you define the ID  and a short description for each hint recognized in the particular language or a language extension. So in our sample project, stateMachine  and stateMachine.tabular  both define their own set of hints.

Mp8

Notice also the Can be used as a default hint  flag that can be set in the inspector. When set to true, the hint will be available to be pushed to editors from the IDE. See the details below, in the "Pushing hints from the IDE" section.

With hints defined, languages can offer the user to switch between notations by adding/removing hints into/from the context. The sample requestTracking  language does it by exposing a presentation  property of an enumeration type to the user. The property influences the collection of hints passed down into the state machine  editor, as specified in the inspector window:

Mp9

Editor hints for Editor Components

Not only editors can have hints specified. Editor Components that  override other editor components have the same capability.

Hints in components1

Pushing hints from the IDE

The hints that have the "Can be used as a default hint" flag enabled, can be pushed by the IDE to the editors as the new defaults. This allows the developers to customize the default projection used for different languages in their IDEs.

One way to customize the projection is to use the Push Editor Hints   action in the editor context menu and select the hints that you want to be pushed as defaults to the active editor frame:

Mp10
Mp11

The active editors will then use projections that match the selected hints.

The second option is to make some hints pushed by the IDE through the corresponding  Settings  panel. These choices are then applied to all editor windows as default preferences.

Mp12

You may consider combining the ability to switch between notations with splitting the editor frame. This allows you to lay several different projections of the same piece of code displayed next to one-another. Your changes in one will be immediately reflected in the other:

Mp13

The right panel has the tabular  notation pushed as the default, which the left panel does not. Both projections visualize the same code.

How to manipulate and apply editor hints programmatically

If you want to create your own UI elements that will adjust editor hints, you'll need to write code that will manipulate the editor hints applied to editor cells.  EditorContext is the entry point for manipulating the hints. You'll need to obtain a  jetbrains.mps.openapi.editor.update.Updater interface implementation, most likely using the  EditorComponent.getUpdater() method. The  Updater interface provides several methods for manipulating hints:

  • setInitialEditorHints()- sets the hints that the editor will start applying from the top of the editor component hierarchy down to all of the components, unless they explicitly remove these hints from the context, returns true if the hints actually changed as a result of this operation

  • getInitialEditorHints()- gets the hints the editor applies to all cells, may return null

  • addExplicitEditorHintsForNode()- adds hints to apply to a particular node's editor and the editors it embeds

  • removeExplicitEditorHintsForNode- removes the specified hints from the list of hints explicitly applied to the node's editor and the editors it embeds

  • getExplicitEditorHintsForNode- gets the list hints of hints explicitly applied to the node's editor and the editors it embeds, may return null

  • clearExplicitHints- clears all explicitly specified hints for all editors

In order to get all hints applied to a particular cell, including the hints added by any of the parent cells, the CellContext must be retrieved, most likely as:
editorContext.getEditorComponent().getUpdater().getCurrentUpdateSession().getCellFactory().getCellContext().getHints()

Also, after changing the hints, the editor needs to be rebuilt in order to reflect the changes. This can be done through the  Updater.update()  method.

Last modified: 26 February 2021