Adding additional Tools (aka Views)
This document describes how to build a new Tool (For Eclipse
users, a Tool in MPS is like a View in Eclipse) for MPS. This can serve as an
example to build arbitrary additional tools into MPS. This text emphasizes to
aspects of Tool development: how to add a new tool to a language and the menu
system, and how to synchronize the view with whatever is currently edited.
In all other ways tools are just Swing UI programs. So for implementing
sophisticated views, one needs Java Swing experience. In this tutorial we will
not focus too much on the intricacies of building a tree view with Swing — an
endeavour that is surprisingly non-trivial!
The Outline Tool itself
The MPS plugin language supports the definition of new Tools. Create a new
Tool and set the name. You can also give it a different
caption and an icon. A default location (bottom, left, right, top) can also be
We now add three fields to the Tool. The first one is used to remember the
project, the second one connects the tool to MPS's message bus, and the third
one remember the
ToolSynchronizer. The ToolSynchronizer updates the
outline view in case the active editor changes. The message bus is IDEA
platform's infrastructure for event distribution. We use it for getting notified
of selection changes in the currently active editor. More on these two later.
We are then ready to implement the three main methods of a Tool:
dispose and getComponent. Here is the code (with comments, please
An Action to Open the Tool
Actions are commands that live in the MPS UI. We have to add an action to open
the outline view. Actions live in the plugins aspect of a language. Actions can
define keyboard shortcuts, captions and icons, as expected. They also declare
action parameters. These define which context needs to be available to be able
to execute the action. This determines the presence of the action in the menu,
and supports delivering that context into the action itself. In our case the
context includes the currently opened project as well as the currently opened
execute method of the action we create and open the Outline tool.
setOutline method is an ordinary method that lives it the Outline Tool
itseld. It simply stores the editor that's passed in.
An Action Group
Menu items are added via groups. To be able to add the Open Outline Action to
the menu system, we have to define a new group. The group defines its contents
(the action, plus a two separators) and it determines where in the
menu it should go. Groups live in the plugin aspect as well.
Managing the Tool Lifecycle
The tool must play nicely with the rest of MPS. It has to listen for a number of
events and react properly. There are two listeners dedicated to this task. The
EditorActivationListener tracks which of the potentially many open editors
is currently active, since the outline view has to show the outline for whatever
editor is currently used by the user. It is also responsible to hooking up
further listeners that deal with the selection status and model changes (see
ModelLifecycleListener tracks lifecycle events for the model
that is edited by the currently active editor. The model may be replaced while
it is edited, for example, by a revert changes VCS operation.
Editor Activation and Focus
EditorActivationListener tracks which of the potentially many open
editors is currently active. It is instantiated and hooked up to the MPS message
bus by the tool as it is created by the following code (already shown above):
In its constructor, it remember the outline tree. Then it sets up a new outline
tree selection listener that listens to selection changes made by the user
in the tree itself.
It then sets up a listener to keep track of the model lifecycle (load, unload,
replace) and it hooks up with the events collector (both explained below).
EditorActivationListener implements FileEditorManagerListener, so
it has to implement the following three methods:
In our case we are interested in the
selectionChanged event, since we'll
have to clean up and hook up all kinds of other listeners. Here is the
cleanupOldEditor method removes existing listeners from the old, now
The next method hooks up the infrastructure to the newly selected editor and its
underlying model. Notice how we use
SNodePointer whenever we keep
references to nodes. This acts as a proxy and deals with resolving the node in
case of a model is replaced and contains a "weak reference" to the actual node,
so it can be garbage collected if the model is unloaded. This is important to
avoid memory leaks!
Tracking the Model Lifecycle
ModelLifecycleListener extends the SModelAdapter class provided
by MPS. We are intereted in the model replacement and hence overload the
modelReplaced method. It is called whenever the currenly held model is
replaced by a new one, e.g. during a VCS rever operation.
In the implementation, we create a new new tree model for the same root. While
this code looks a bit nonsensical, note that we use an
internally which automatically re-resolves the proxied node in the new model.
We also add ourselves as a listener to the new model's descriptor to be notified
of subseqent model replacement events.
Synchronizing Node Selections
Tracking Editor Selection
This one updates the selection (and expansion status) of the tree as the
selected node in the currently active editor changes. We have already made sure
(above) that the outline view's tree is synchronized with the currently active
The class extends MPS'
SingularSelectionListenerAdapter because we are
only interested in single node selections. We overwrite the
selectionChangedTo method in the following way:
Tracking Changes in the Model Structure
The tree structure in the outline view has to be updated if nodes are added,
changed (renamed), moved or deleted in the editor. Tree change events can be
quite granular, and to avoid overhead, MPS collects them into batches related to
more coarse-grained commands. By using MPS'
EventsCollector base class, we
can get notified when a significant batch of events has happened, and then
inspect the event list for those that we are interested in using a visitor.
ModelChangeListener performs this task. To do so, we have to implement
eventsHappened method. We get a list of events, and use a an inner
class that extends
SModelEventVisitorAdapter to visit the events and react
to those that we are interested in.
ModelChangeVisitor inner class, which acts as the visitor to notify
the tree, overrides
visitPropertyEvent to find out about nodes whose
properties have changed in the current batch. It then notifies all the listeners
of the tree model.
It also overwrites
visitChildEvent to get notified of child
additions/deletions of nodes. Except that the API in
JTree is a bit
annoying, the following commented code should be clear about what it does:
The way back: Tracking Tree Selection
JTree's selection happens by implementing Swing's
TreeSelectionListener and overwriting it's valueChanged method the
Swing's Artifacts: Tree Model and Renderer
In this section I want to describe a couple of interesting MPS-specific aspects
of the implementation of the Swing artifacts.
Tree Cell Renderer
The tree cell renderer is responsible for rendering the cells in the tree. It
getPresentation method on the nodes, and the IconManager to
get (a cached version of) the icon for the respective node.
The tree model is interesting since we include only those children in the tree
node whose concept has been annotated with the
ShowConceptInOutlineAttribute attribute. It is stored in the
storeInOutline role. In the tree model, we have to filter the children of
a node for the presence of this attribute. Here is the respective helper method: