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. This binding is usually added by the respective generator fragment.
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).
To register context menus for specific node types of your Ecore model, we need to:
implement 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.
The Xtext code generator creates a subclass of DefaultContentOutlineNodeAdapterFactory. All we need to do is specify a list of types that we later want to bind context menu contributions to.
public class MyDslContentOutlineNodeAdapterFactory extends
DefaultContentOutlineNodeAdapterFactory {
private static final Class<?>[] types = { Attribute.class };
public Class<?>[] getAdapterList() {
return types;
}
}
If you want to bind context menu actions to nodes representing Attribute and Entity, you need to change the declaration of _types as follows:
private static final Class<?>[] types = { Attribute.class, Entity.class };
You can now 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.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></adapt>.
Xtext also provides a quick outline: If you press CTRL-O in an Xtext editor, the outline of the model is shown in a popup window. The quick outline also supports drill-down search with wildcards. To enable the quick outline, you have to put the QuickOutlineFragment into your workflow.