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.
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 nodes, you can customize the outline by implementing your own IOutlineTreeProvider (src).
If your workflow defines the OutlineTreeProviderFragment (src), Xtext generates a stub for your own IOutlineTreeProvider (src) that allows you to customize every aspect of the outline by inheriting the powerful customization methods of DefaultOutlineTreeProvider (src). The following sections show how to do fill this stub with life.
Each node the outline tree is an instance of IOutlineNode (src). The outline tree is always rooted in a DocumentRootNode (src). This node is automatically created for you. Its children are the root nodes in the displayed view.
An EObjectNode (src) represents a model element. By default, Xtext creates an EObjectNode (src) for each model element in the node of its container. Nodes are created by calling the method createNode(parentNode, modelElement) which delegates to createEObjectNode(..) if not specified differently.
To change the children of specific nodes, you have to implement the method
_createChildren(parentNode,
parentModelElement)
protected void _createChildren(DocumentRootNode parentNode,
DomainModel domainModel) {
for (AbstractElement element : domainModel.getElements()) {
createNode(parentNode, element);
}
}
You can choose not to create any node in the _createChildren() method. Because the outline nodes are calculated on demand, the UI will show you an expandable node that doesn't reveal any children if expanded. This might be confuse your users a bit. To overcome this shortcoming, you have to implement the method _isLeaf(modelElement) with the appropriate argument type, e.g.
// feature nodes are leafs and not expandable
protected boolean _isLeaf(Feature feature) {
return true;
}
Xtext provides a third type of node: EStructuralFeatureNode (src). It is used to represent a feature of a model element rather than element itself. The following simplified snippet from Xtend2 illustrates how to use it:
protected void _createChildren(DocumentRootNode parentNode,
XtendFile xtendFile) {
// show a node for the attribute XtendFile.package
createEStructuralFeatureNode(parentNode,
xtendFile,
Xtend2Package.Literals.XTEND_FILE__PACKAGE,
getImageForPackage(),
xtendFile.getPackage(),
true);
// show a container node for the list reference XtendFile.imports
// the imports will be shown as individual child nodes automatically
createEStructuralFeatureNode(parentNode,
xtendFile,
Xtend2Package.Literals.XTEND_FILE__IMPORTS,
getImageForImportContainer(),
"import declarations",
false);
createEObjectNode(parentNode, xtendFile.getXtendClass());
}
Of course you can add further custom types of nodes. For consistency, make sure to inherit from AbstractOutlineNode (src). To instantiate these, you have to implement _createNode(parentNode, semanticElement) with the appropriate parameter types.
You can also customize the icons and texts for an outline node. By default, Xtext uses the label provider of your language. If you want the labels to be specific to the outline, you can override the methods _text(modelElement) and _image(modelElement) in your DefaultOutlineTreeProvider (src).
Note that the method _text(modelElement) can return a String or a StyledString. The StylerFactory (src) can be used to create StyledStrings, like in the following example:
@Inject
private StylerFactory stylerFactory;
public Object _text(Entity entity) {
if(entity.isAbstract()) {
return new StyledString(entity.getName(),
stylerFactory
.createXtextStyleAdapterStyler(getTypeTextStyle())));
else
return entity.getName();
}
protected TextStyle getTypeTextStyle() {
TextStyle textStyle = new TextStyle();
textStyle.setColor(new RGB(149, 125, 71));
textStyle.setStyle(SWT.ITALIC);
return textStyle;
}
To access images we recommend to use the PluginImageHelper (src).
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 a filter action to your outline. Filter actions must extend AbstractFilterOutlineContribution (src) to ensure that the action toggle state is handled correctly. Here is an example form our domain model example:
public class FilterOperationsContribution
extends AbstractFilterOutlineContribution {
public static final String PREFERENCE_KEY =
"ui.outline.filterOperations";
@Inject
private PluginImageHelper imageHelper;
@Override
protected boolean apply(IOutlineNode node) {
return !(node instanceof EObjectNode)
|| !((EObjectNode) node).getEClass()
.equals(DomainmodelPackage.Literals.OPERATION);
}
@Override
public String getPreferenceKey() {
return PREFERENCE_KEY;
}
@Override
protected void configureAction(Action action) {
action.setText("Hide operations");
action.setDescription("Hide operations");
action.setToolTipText("Hide operations");
action.setImageDescriptor(getImageDescriptor());
}
protected ImageDescriptor getImageDescriptor(String imagePath) {
return ImageDescriptor.createFromImage(
imageHelper.getImage("Operation.gif"));
}
}
The contribution must be bound in the MyDslUiModule like this
public void configureFilterOperationsContribution(Binder binder) {
binder
.bind(IOutlineContribution.class).annotatedWith(
Names.named("FilterOperationsContribution"))
.to(FilterOperationsContribution.class);
}
Xtext already adds a sorting action to your outline. By default, nodes are sorted lexically by their text. You can change this behavior by binding your own OutlineFilterAndSorter.IComparator (src).
A very common use case is to group the children by categories first, e.g. show the imports before the types in a package declaration, and sort the categories separately. That is why the SortOutlineContribution.DefaultComparator (src) has a method getCategory(IOutlineNode) that allows to specify such categories. The example shows how to use such categories:
public class MydslOutlineNodeComparator extends DefaultComparator {
@Override
public int getCategory(IOutlineNode node) {
if (node instanceof EObjectNode)
switch((EObjectNode) node).getEClass().getClassifierID())) {
case MydslPackage.TYPE0:
return -10;
case MydslPackage.TYPE1:
return -20;
}
return Integer.MIN_VALUE;
}
}
As always, you have to declare a binding for your custom implementation in your MyDslUiModule:
@Override
public Class<? extends IComparator>
bindOutlineFilterAndSorter$IComparator() {
return MydslOutlineNodeComparator.class;
}
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 (src) into your workflow.