Xtext provides an outline view to help you navigate your models. By default, it provides a hierarchical view on your model and allows you to sort tree elements alphabetically. Selecting an element in the outline will highlight the corresponding element in the text editor. Users can choose to synchronize the outline with the editor selection by clicking the Link with Editor button.
You can customize various aspects of the outline by providing implementation for its various interfaces. The following sections show how to do this.
In its default implementation, the outline view shows the containment hierarchy of your model. This should be sufficient in most cases. If you want to adjust the structure of the outline, i.e., by omitting a certain kind of node or by introducing additional even virtual nodes, you customize the outline by implementing ISemanticModelTransformer.
The Xtext wizard creates an empty transformer class (
MyDslTransformer
) for your convenience. To transform the semantic model delivered by the Xtext parser, you need to provide transformation methods for each of the EClasses that are of interest:
public class MyDslTransformer extends
AbstractDeclarativeSemanticModelTransformer {
/**
* This method will be called by naming convention:
* - method name must be createNode
* - first param: subclass of EObject
* - second param: ContentOutlineNode
*/
public ContentOutlineNode createNode(
Attribute semanticNode, ContentOutlineNode parentNode) {
ContentOutlineNode node = super.newOutlineNode(semanticNode, parentNode);
node.setLabel("special " + node.getLabel());
return node;
}
public ContentOutlineNode createNode(
Property semanticNode, ContentOutlineNode parentNode) {
ContentOutlineNode node = super.newOutlineNode(semanticNode, parentNode);
node.setLabel("pimped " + node.getLabel());
return node;
}
/**
* This method will be called by naming convention:
* - method name must be getChildren
* - first param: subclass of EObject
*/
public List<EObject> getChildren(Attribute attribute) {
return attribute.eContents();
}
public List<EObject> getChildren(Property property) {
return NO_CHILDREN;
}
}
To make sure Xtext picks up your new outline transformer, you have to register your implementation with your UI module:
public class MyDslUiModule extends AbstractMyDslUiModule {
@Override
public Class<? extends ISemanticModelTransformer>
bindISemanticModelTransformer() {
return MyDslTransformer.class;
}
...
}
Often, you want to allow users to filter the contents of the outline to make it easier to concentrate on the relevant aspects of the model. To add filtering capabilities to your outline, you need to add AbstractFilterActions to the outline. Actions can be contributed by implementing and registering a DeclarativeActionBarContributor.
To register a
DeclarativeActionBarContributor
, add the following lines to your
MyDslUiModule
class:
public class MyDslUiModule extends AbstractMyDslUiModule {
@Override
public Class<? extends IActionBarContributor> bindIActionBarContributor() {
return MyDslActionBarContributor.class;
}
...
}
The action bar contributor will look like this:
public class MyDslActionBarContributor extends
DeclarativeActionBarContributor {
public Action addFilterParserRulesToolbarAction(
XtextContentOutlinePage page) {
return new FilterFooAction(page);
}
}
Filter actions must extend
AbstractFilterAction
(this ensures that the action toggle state is handled correctly):
public class FilterFooAction extends AbstractFilterAction {
public FilterFooAction(XtextContentOutlinePage outlinePage) {
super("Filter Foo", outlinePage);
setToolTipText("Show / hide foo");
setDescription("Show / hide foo");
setImageDescriptor(Activator.getImageDescriptor("icons/foo.gif"));
setDisabledImageDescriptor(
Activator.getImageDescriptor("icons/foo.gif"));
}
@Override
protected String getToggleId() {
return "FilterFooAction.isChecked";
}
@Override
protected ViewerFilter createFilter() {
return new FooOutlineFilter();
}
}
The filtering itself will be performed by
FooOutlineFilter
:
public class FooOutlineFilter extends ViewerFilter {
@Override
public boolean select(
Viewer viewer, Object parentElement, Object element) {
if ((parentElement != null)
&& (parentElement instanceof ContentOutlineNode)) {
ContentOutlineNode parentNode = (ContentOutlineNode) parentElement;
EClass clazz = parentNode.getClazz();
if (clazz.equals(MyDslPackage.Literals.ATTRIBUTE)) {
return false;
}
}
return true;
}
}
You might want to register context menu actions for specific elements in the outline, e.g. to allow users of your DSL to invoke a generator or to validate the selected element. As all elements in the outline are
ContentOutlineNodes, you cannot easily register an
Object contribution. (Besides, using the extension point
org.eclipse.ui.popupMenus
is regarded somewhat old school – you should rather use the new command and expression framework, as depicted below).
So to register context menus for specific node types of your Ecore model, you need to do the following:
register a IContentOutlineNodeAdapterFactory which will translate ContentOutlineNodes to their underlying node type
register a menu contribution to add a command / handler pair to the context menu for the specific node types you’re interested in.
Create a subclass of
DefaultContentOutlineNodeAdapterFactory - this class contains some base infrastructure to manage adapters for your node types. In your subclass, you need to override
getAdapterList()
and return an array of classes. This is the list of classes you want to add context menus to:
public class MyDslContentOutlineNodeAdapterFactory extends
DefaultContentOutlineNodeAdapterFactory {
@SuppressWarnings("unchecked")
private static final Class[] types = { Attribute.class };
@SuppressWarnings("unchecked")
public Class[] getAdapterList() {
return types;
}
}
Register this class with your
MyDslUiModule
to make it available to your DSL editor:
public class MyDslUiModule extends AbstractMyDslUiModule {
...
public Class<? extends IContentOutlineNodeAdapterFactory>
bindIContentOutlineNodeAdapterFactory() {
return org.example.myDSLContentOutlineNodeAdapterFactory.class;
}
...
}
After you have registered the IContentOutlineNodeAdapterFactory, you can add command / handler pairs to the context menu.
First, you need to define a command – it will serve as a handle to glue together the handler and the menu contribution:
<extension
point="org.eclipse.ui.commands">
<command
id="org.example.mydsl.ui.editor.outline.SampleOutlineCommand"
name="Sample Command"
description="Just a sample command">
</command>
</extension>
Next, you need to define a handler which will eventually execute the code to operate on the selected node. Please pay special attention to the attribute
commandId
- it must match the
id
attribute of your command.
<extension
point="org.eclipse.ui.handlers">
<handler
class="org.example.mydsl.ui.editor.outline.SampleOutlineNodeHandler"
commandId="org.example.mydsl.ui.editor.outline.SampleOutlineCommand">
</handler>
</extension>
Finally, define a
menuContribution
to add the command to the context menu:
<extension
point="org.eclipse.ui.menus">
<menuContribution
locationURI="popup:org.eclipse.xtext.ui.common.outline?after=additions">
<command
commandId="org.example.mydsl.ui.editor.outline.SampleOutlineCommand"
label="Sample action registered for Attributes">
<visibleWhen checkEnabled="false">
<iterate>
<adapt type="org.example.mydsl.Attribute" />
</iterate>
</visibleWhen>
</command>
</menuContribution>
</extension>
Again, pay attention to the
commandId
attribute. The connection between your node type(s) and the menu contribution is made by the part
<adapt type="org.example.mydsl.Attribute" />
.