Domain specific languages are a great tool when we want to give our users control over complex aspects in our applications; and in most cases, experienced uses can learn syntax and semantics of a well-designed DSL quickly. But on the other hand, there are also inexperienced users, who usually struggle with DSLs and do not want to deal with textual input. Instead, they are used to graphical user interfaces which help them to grasp the structure of information and to enter new data.
Last week, I had a project, in which I needed to provide both groups of users with a suitable user interface. In other words, I needed to create an editor, which provides both an Xtext source editor for my DSL, and a GUI editor for the model behind the DSL.
I started to search the Internet for some hints on how to do this but I did not find a complete example. Basically, to combine multiple editors into a single one, we have to subclass and implement an
org.eclipse.ui.part.MultiPageEditorPart. But I did not find more than hints about how to share the model between an Xtext editor and another editor. So, I started experimenting and after some time I was successful, and I wanted to let you participate in my results. Maybe this helps someone who is looking for an example as well.
For the rest of this post, I will use the state machine example that comes with the Xtext distribution as a starting point. To demonstrate the solution I will add a GUI editor for events to the generated Xtext editor. For brevity, I will only show the relevant parts. The full modified example source code is available on GitHub.
Step 1: Setup
To get started, we create the State Machine Example (File > New… > Xtext > Xtext State-Machine Example) in our workspace. The example code is a bit old; to be able to use Java 8 features, we need to switch the Execution Environment, Java Compiler Settings and Java Library Path to Java 1.8.
Step 2: Create the MultiPageEditor
Inside the UI bundle, I have created a new package
org.eclipse.xtext.example.fowlerdsl.ui.editor for my editor code. In there, I have created the class
StatemachineMultiPageEditor as follows:
This implementation more or less just wraps the Xtext editor. To enable it in the runtime application, we can edit the
plugin.xml and replace the
XtextEditor which is configured by Xtext with our own implementation:
When we start the runtime application, we should be able to create a state machine file (see
README in the example project for instructions) and when opening it, we should see that it opens in our own
StatemachineMultiPageEditor, which has a single tab called “Source”.
Step 3: Create the UI editor
Now comes the interesting part. To implement our own editor, we need a suitable editor input which provides our editor with access to the
XtextEditor model, which is the
XtextDocument. So, I decided to wrap the
IXtextDocument in an
IEditorInput for our own editor:
Based on this, we can implement the GUI editor. For this simple example, we will just have a
ListViewer showing the list of
Events and three buttons (add, edit, delete).
The interesting methods here are
addEvent(). They use the
XtextDocument to access the model in its (dirty) state and/or to manipulate it. It is important to note that the custom code should not hold on to the EMF instances retrieved this way, because when the text is reparsed, Xtext usually throws away old
EObject instances and create new ones. Likewise, when modifying the model, make sure that you use the
XtextResource and own copies of the information to navigate to the desired model element(s), instead of using previously retrieved
Step 4: Add the UI editor as a page to the MultiPageEditor
Now we need to add our
EventEditor to the
StatemachineMultiPageEditor. We let Guice inject our editor just like the
XtextEditor. Then we create the editor input and add a second page with our new
Finally, we need to take care of the synchronization between the editors. We could have our editor install a listener on the
XtextDocument, so that we get notified on any change. But I decided that it should be enough (and is simpler) to refresh the GUI whenever the user switches the page and the
EventEditor becomes visible. To do this, we override the method
The resulting changes to the StatemachineMultiPageEditor look like this:
And here is a small demo of the running editor:
Odds and Ends
Of course, this example is only a proof of concept and not fully implemented in all detail. For example, validation in the GUI is completely missing. So far, I have not found a good way to include Xtext validation in the GUI (it would be nice to have a name or code checked against the DSL syntax instead of writing an own validator). Also, some additional cases must be handled (for example, what happens if the list of events becomes empty or if there are events with the same name etc.)
Additionally, to have a good interoperability between DSL source and GUI, you have to customize the formatter so that generated model elements are serialized nicely (not like in my example). Also keep in mind that hidden tokens (such as comments) are not represented in the model itself and could be deleted by accident in model modification operations.