Associating Xtext editors to file names, instead of file extensions
Join the DZone community and get the full member experience.
Join For FreeI’m currently working on a Xtext-based editor for Google’s BUILD language, as described in our Google Engineering Blog. BUILD files do not contain an extension, they are simply named “BUILD.” This naming convention make working with Xtext quite challenging. I needed to change Xtext’s default behavior: instead of associating Xtext editors to file extensions, I needed to associate them to file names. In this blog post I’m describing how I was able to accomplish this.
In this post I’ll be using a brand-new Xtext project. To keep things simple, I’ll use the default file extension (mydsl) and grammar. You can get the source code of this post from Xtext Samples, a new open source project where I plan to store the code I use in Xtext-related posts. All code is released under the Eclipse Public License (EPL) 1.0.
The following steps assume we are going to associate our Xtext editor with the file name “MyDsl.”
- Modify the plug-in’s XtextEditor to make it understand file names, instead of file extensions.
- Create a ContentHandler that describes the content of files with name “MyDsl.” As part of this step we need to create a new content type for “MyDsl” files.
- Create an IResourceServiceProvider that provides services (e.g. validation, content description, encoding) to files with name “MyDsl.”
- Register the classes created in the previous steps, to make them visible to EMF and Xtext.
1. Modify the plug-in’s XtextEditor to make it understand file names
This is the easiest of all steps:
- Open the file plugin.xml in the “ui” project.
- Select the “plugin.xml” tab at the bottom of the editor, to edit the XML code directly.
- In the editor element, replace extensions="mydsl" with filenames="MyDsl"
2. Create a ContentHandler that describes the content of files with name “MyDsl”
Here is a simplified version of our ContentHandler. You can find the complete file here.
public class MyDslContentHandler extends ContentHandlerImpl { public static final String MY_DSL_FILE_CONTENT_TYPE = "com.google.eclipse.MyDsl"; @Override public boolean canHandle(URI uri) { return isMyDslFile(uri); } @Override public Map<String, Object> contentDescription(URI uri, InputStream inputStream, Map<?, ?> options, Map context) throws IOException { Map<String, Object> description = super.contentDescription(uri, inputStream, options, context); if (canHandle(uri)) { description.put(VALIDITY_PROPERTY, VALID); description.put(CONTENT_TYPE_PROPERTY, MY_DSL_FILE_CONTENT_TYPE); } return description; } }
Line 2: We define a new content type for “MyDsl” files. We keep the constant public since we will be using it later.
Line 4: We specify that file names with name “MyDsl” can be handled by the ContentHandler. We call the utility method isMyDslFile(URI) from the class URIs.
Line 8: We declare the content of “MyDsl” files as valid and associate its type to the one defined in line 2.
3. Create an IResourceServiceProvider that provides services to files with name “MyDsl”
Here is a simplified version of our IResourceServiceProvider. You can find the complete file here.
public class MyDslResourceServiceProvider extends DefaultResourceServiceProvider { @Override public boolean canHandle(URI uri) { return isMyDslFile(uri); } }
We call the utility method isMyDslFile(URI) from the class URIs to specify that this IResourceServiceProvider can handle files with names “MyDsl.”
4. Register the classes created in the previous steps, to make them visible to EMF and Xtext
Now it’s time to wire up everything together. First, we bind IResourceServiceProvider to our MyDslResourceServiceProvider in the plug-in runtime module:
public Class<? extends IResourceServiceProvider> bindIResourceServiceProvider() { return MyDslResourceServiceProvider.class; }
Now we tell Xtext and EMF, in MyDslUiModule‘s constructor, to understand files named “MyDsl.”
public class MyDslUiModule extends AbstractMyDslUiModule { public MyDslUiModule(AbstractUIPlugin plugin) { super(plugin); configureXtextToWorkWithFileNames(new InjectorProvider()); } }
We pass an InjectorProvider that obtains the plugin’s Injector only at the moment when it is needed. At this point we cannot pass the Injector directly, since it has not been fully constructed when configureXtextToWorkWithFileNames is called.
The method configureXtextToWorkWithFileNames(InjectorProvider) is defined in XtextSetup:
final class XtextSetup { static void configureXtextToWorkWithFileNames(InjectorProvider injectorProvider) { register(new MyDslContentHandler()); register(new ResourceFactoryDescriptor(injectorProvider)); register(new ResourceServiceProvider(injectorProvider)); } private static void register(ContentHandler h) { ContentHandler.Registry registry = ContentHandler.Registry.INSTANCE; registry.put(HIGH_PRIORITY, h); } private static void register(Resource.Factory.Descriptor d) { Resource.Factory.Registry registry = Resource.Factory.Registry.INSTANCE; registry.getContentTypeToFactoryMap().put(MY_DSL_FILE_CONTENT_TYPE, d); } private static void register(Provider<IResourceServiceProvider> p) { IResourceServiceProvider.Registry registry = IResourceServiceProvider.Registry.INSTANCE; registry.getContentTypeToFactoryMap().put(MY_DSL_FILE_CONTENT_TYPE, p); } }
Line 4: We register the ContentHandler we created in step 2.
Line 5: We associate the IResourceFactory in the plug-in’s Guice module with the content type defined in step 2.
Line 6: We associate the IResourceServiceProvider we created in step 3 (and later on registered in the plug-in’s Guice module) with the content type defined in step 2.
Testing the editor
To run the editor, right-click any of the projects and select “Run As” > “Eclipse Application” from the context menu.
Here is a screenshot of the editor opening a file named “MyDsl”
Summary
In this post I have shown how to make Xtext understand file names in four easy steps. You can find the code for this post in the project Xtext Samples. You can checkout the code, browse it or download it.
Even though I spent a good amount of time reading Xtext and EMF code to figure this out, I cannot claim my solution is the best one. Please let me know if you know a better way to do what I described in this post :)
Feedback is always welcome.
Opinions expressed by DZone contributors are their own.
Trending
-
Constructing Real-Time Analytics: Fundamental Components and Architectural Framework — Part 2
-
Introduction to Domain-Driven Design
-
Tech Hiring: Trends, Predictions, and Strategies for Success
-
Avoiding Pitfalls With Java Optional: Common Mistakes and How To Fix Them [Video]
Comments