Home > Xtext > Xtext: Calling the Generator from a Context Menu

Xtext: Calling the Generator from a Context Menu

Xtext offers the user to implement an Xtend Class that implements the org.eclipse.xtext.generator.IGenerator Interface. By Default this Generator is called by the Builder through a org.eclipse.xtext.builder.IXtextBuilderParticipant when saving the file. But what to do if I want to call the Generator explicitely through a context menu entry on the file as shown in the screenshot below? This will be shown in the Following example.

Grammar & IGenerator

We first start with writing the Grammar and after generating the language we implement the Generator Stub Xtext created for us.

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"

Model:
	greetings+=Greeting*;
	
Greeting:
	'Hello' name=ID ('from' from=[Greeting])?'!';
package org.xtext.example.mydsl.generator

import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
import org.xtext.example.mydsl.myDsl.Greeting

import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.*

class MyDslGenerator implements IGenerator {
	
	override void doGenerate(Resource resource, IFileSystemAccess fsa) {
		for (g : resource.allContentsIterable.filter(typeof(Greeting))) {
			fsa.generateFile(g.name+".txt",'''
				Hello «g.name» «IF g.from != null»from «g.from.name»«ENDIF»!
			''')
		}
	}
}

Disabling the IBuilderParticipant

Then we go to the UI Projects Plugin.xml and disable the default registration of an IXtextBuilderParticipant

<!--
   <extension
         point="org.eclipse.xtext.builder.participant">
      <participant
            class="org.xtext.example.mydsl.ui.MyDslExecutableExtensionFactory:org.eclipse.xtext.builder.IXtextBuilderParticipant">
      </participant>
   </extension>
-->

Setting up the Context Menu

Then we setup the context menu with Eclipse means

<extension
        point="org.eclipse.ui.handlers">
     <handler
           class="org.xtext.example.mydsl.ui.MyDslExecutableExtensionFactory:org.xtext.example.mydsl.ui.handler.GenerationHandler"
           commandId="org.xtext.example.mydsl.ui.handler.GenerationCommand">
     </handler>
     
  </extension>
  
  <extension
        point="org.eclipse.ui.commands">
        <command name="Generate Code"
              id="org.xtext.example.mydsl.ui.handler.GenerationCommand">
        </command>
  </extension>
  
  <extension point="org.eclipse.ui.menus">
    <menuContribution locationURI="popup:org.eclipse.jdt.ui.PackageExplorer">
        <command
            commandId="org.xtext.example.mydsl.ui.handler.GenerationCommand"
            style="push">
            <visibleWhen
                  checkEnabled="false">
                  <iterate>
       <adapt type="org.eclipse.core.resources.IResource">
          <test property="org.eclipse.core.resources.name" 
                value="*.mydsl"/>
       </adapt>
    </iterate>
            </visibleWhen>
        </command>
    </menuContribution>
	</extension>

Implementing the Handler / Calling the Generator

The last thing we have to do is to call the IGenerator from the handler class.
this could look like

public class GenerationHandler extends AbstractHandler implements IHandler {
	
	@Inject
	private IGenerator generator;

	@Inject
	private Provider<EclipseResourceFileSystemAccess> fileAccessProvider;
	
	@Inject
	IResourceDescriptions resourceDescriptions;
	
	@Inject
	IResourceSetProvider resourceSetProvider;
	
	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		
		ISelection selection = HandlerUtil.getCurrentSelection(event);
		if (selection instanceof IStructuredSelection) {
			IStructuredSelection structuredSelection = (IStructuredSelection) selection;
			Object firstElement = structuredSelection.getFirstElement();
			if (firstElement instanceof IFile) {
				IFile file = (IFile) firstElement;
				IProject project = file.getProject();
				IFolder srcGenFolder = project.getFolder("src-gen");
				if (!srcGenFolder.exists()) {
					try {
						srcGenFolder.create(true, true,
								new NullProgressMonitor());
					} catch (CoreException e) {
						return null;
					}
				}

				final EclipseResourceFileSystemAccess fsa = fileAccessProvider.get();
				fsa.setOutputPath(srcGenFolder.getFullPath().toString());
				
				URI uri = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
				ResourceSet rs = resourceSetProvider.get(project);
				Resource r = rs.getResource(uri, true);
				generator.doGenerate(r, fsa);
				
			}
		}
		return null;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

}

Give it a try
We start a runtime app, create a bunch of model files that refernce each other,
try the genenerator – and hey – it works.

And how if i want to call the generator from the context menu of the open editor

I register the command for the editor and write a Handler like this one

<extension point="org.eclipse.ui.menus">
    <menuContribution locationURI="popup:#TextEditorContext?after=additions">
        <command
            commandId="org.xtext.example.mydsl.ui.handler.GenerationCommand"
            style="push">
            <visibleWhen
	                  checkEnabled="false">
	               <reference
	                     definitionId="org.xtext.example.mydsl.MyDsl.Editor.opened">
	               </reference>
	            </visibleWhen>
        </command>
    </menuContribution>
</extension>
public class GenerationHandler extends AbstractHandler implements IHandler {
	
	@Inject
	private IGenerator generator;

	@Inject
	private Provider<EclipseResourceFileSystemAccess> fileAccessProvider;
	
	@Inject
	IResourceDescriptions resourceDescriptions;
	
	@Inject
	IResourceSetProvider resourceSetProvider;
	
	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		
		IEditorPart activeEditor = HandlerUtil.getActiveEditor(event);
		IFile file = (IFile) activeEditor.getEditorInput().getAdapter(IFile.class);
		if (file != null) {
			IProject project = file.getProject();
			IFolder srcGenFolder = project.getFolder("src-gen");
			if (!srcGenFolder.exists()) {
				try {
					srcGenFolder.create(true, true,
							new NullProgressMonitor());
				} catch (CoreException e) {
					return null;
				}
			}
	
			final EclipseResourceFileSystemAccess fsa = fileAccessProvider.get();
			fsa.setOutputPath(srcGenFolder.getFullPath().toString());
			
			
			if (activeEditor instanceof XtextEditor) {
				((XtextEditor)activeEditor).getDocument().readOnly(new IUnitOfWork<Boolean, XtextResource>() {
				
					@Override
					public Boolean exec(XtextResource state)
							throws Exception {
						generator.doGenerate(state, fsa);
						return Boolean.TRUE;
					}
				});
				
			}
		}
		return null;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

}
About these ads
  1. Markus
    November 16, 2011 at 09:29

    Thank you very much for this post, it was exactly what I was looking for!!!

    Is there also a possibility to enhance this approach, by automatically create a ‘src-gen’ folder, if there is none in the project? (if no ‘src-gen’ folder is available, no source files are generated, right?!)

    regards,
    Markus

    • Markus
      November 16, 2011 at 11:54

      Sorry, it is allready implemented so in the example….
      My feault!!!

      regards,
      Markus

  2. Markus
    November 16, 2011 at 14:17

    Hello Christian,

    is it possible, that the source code generation only works, when the project in the generator-UI is a Java Project?

    It seems like, that the input model is then not contained in the LazyLinkingResource, when the doGenrate() is called (rs->resources->data[0]:LazyLinkingResource->contents = null)

    To me it seems like the problem could be in the resourceSetProvider.get() call (in line:
    ResourceSet rs = resourceSetProvider.get(project);)

    It would be great if there is a work around to not mandatorily use Java-Project in the generator-UI.

    Kind regards,
    Markus

    • Markus
      November 16, 2011 at 18:23

      with your second apporach:

      @Override
      public Boolean exec(XtextResource state)
      throws Exception {
      generator.doGenerate(state, fsa);
      return Boolean.TRUE;
      }

      it works fine…

      Kind regards,
      Markus

    • November 18, 2011 at 16:56

      Hi,

      it works for me with a simple project too – in both variants

      ~Christian

  3. Alfredo Capozucca
    April 2, 2012 at 08:44

    I’m wondering if it is possible to generated the files in a src-gen directory which is placed in a different project to the one where the source models are placed.

    • April 2, 2012 at 09:17

      Since i explicitely configure the src-gen folder i dont see why it should be a problem to configure explicitely another folder

  4. June 1, 2012 at 06:44

    Thank you Christian!
    That’s how we use our Xtend generators now. Meanwhile, the EclipseResourceFileSystemAccess is deprecated (replaced by Ecl…Access2) and I would be interested in how your example would look like now. Beside the OutputPath, the new Ecl…Access2 needs a Monitor, a Project and a PostProcessor additionally…

    Best regards,
    Holger

    • June 2, 2012 at 17:33

      Hi,

      this should be rather straight forward:
      – the project we have already in our hand
      – as a first step we can use a NullProgressMonitor (or we wrap the doing of our command into a job and take the jobs IProgressMonitor – nothing really Xtext Specific)
      – for the IFileCallback provide a Implementation (that e.g. is doing nothing) yourself.

  5. Markus
    July 4, 2012 at 12:12

    Hi Christian!

    EclipseResourceFileSystemAccess is deprecation in Xtext SDK Version 2.3.0. If I use EclipseResourceFileSystemAccess2 I get a NullPointerException when staring the generation process:
    ——————————————————————————————————————–
    java.lang.NullPointerException
    at org.eclipse.xtext.builder.EclipseResourceFileSystemAccess2.generateFile(EclipseResourceFileSystemAccess2.java:116)
    at org.eclipse.xtext.generator.AbstractFileSystemAccess.generateFile(AbstractFileSystemAccess.java:74)
    at merses.ip.hdlGEN.morphGEN.generator.MIPGenerator.doGenerate(MIPGenerator.java:76)
    at merses.ip.hdlGEN.morphGEN.ui.handler.GenerationHandler$2.run(GenerationHandler.java:159)
    at org.eclipse.core.internal.jobs.Worker.run(Worker.java:54)
    ——————————————————————————————————————–

    It would be great if you can give me a hint to solve this problem?

    Kind regards!
    Markus

    • July 4, 2012 at 20:01

      Hi did you do what i suggested regarding IProgressMonitor and IFileCallback

      • Markus
        July 6, 2012 at 08:19

        Thanks Christian,
        now i did and now it works! Beside the setting of IProgressMonitor also the IProject

        > fsa.setProject(project);

        has to be set for usage of EclipseResourceFileSystemAccess2.

        Have a nice weekend!
        Markus

        PS: setting IFileCallback was not necessary in my project

  6. Marcus Mathioudakis
    July 5, 2012 at 10:12

    Hi Christian,

    I was just wondering how we would go about things if we wanted to invoke the generator from a java code context instead of a menu context. So if we could just create a file in the code, and then pass it to the code generator for our DSL? Furthermore could we use the JvmModelInferrer instead of the generator?

    Thanks!

    Marcus

    • July 5, 2012 at 17:40

      Hi i guess this goes beyond my blog post but it should be surely possible

  7. Michael Colburn
    October 15, 2012 at 15:06

    Christian,

    Can you please provide the import statements for the GenerationHandler class? Also, what project and package should the class file be placed into?

    Thanks!

    • October 15, 2012 at 18:07

      package org.xtext.example.mydsl.ui.handler;

      import org.eclipse.core.commands.AbstractHandler;
      import org.eclipse.core.commands.ExecutionEvent;
      import org.eclipse.core.commands.ExecutionException;
      import org.eclipse.core.commands.IHandler;
      import org.eclipse.core.resources.IFile;
      import org.eclipse.core.resources.IFolder;
      import org.eclipse.core.resources.IProject;
      import org.eclipse.core.runtime.CoreException;
      import org.eclipse.core.runtime.NullProgressMonitor;
      import org.eclipse.emf.common.util.URI;
      import org.eclipse.emf.ecore.resource.Resource;
      import org.eclipse.emf.ecore.resource.ResourceSet;
      import org.eclipse.jface.viewers.ISelection;
      import org.eclipse.jface.viewers.IStructuredSelection;
      import org.eclipse.ui.handlers.HandlerUtil;
      import org.eclipse.xtext.builder.EclipseResourceFileSystemAccess;
      import org.eclipse.xtext.generator.IGenerator;
      import org.eclipse.xtext.resource.IResourceDescriptions;
      import org.eclipse.xtext.ui.resource.IResourceSetProvider;

      import com.google.inject.Inject;
      import com.google.inject.Provider;

      • Michael
        October 15, 2012 at 18:35

        Thank you, Christian!

  8. Michael Colburn
    October 15, 2012 at 20:21

    Christian,

    The nice example you provided seems to rely on the user having first opened the file in the editor. My use case is as follows: no matter which DSL file is selected, whether it has been opened in the editor or not, I need to generate code when the user right-clicks the file and selects “Generate Code”. Currently, if the file has not been opened, the ActiveEditor is null. Any suggestions or solutions? Thanks!

    • October 15, 2012 at 20:32

      Hi,
      i actually describe both usecases in my blogpost. have a look at the first version of the handler.

      • Michael Colburn
        October 15, 2012 at 22:14

        Ah, yes! I started out copying from the latter use case instead of the initial one. Thanks, it works well!

  9. Tommy Dvorczak
    July 18, 2013 at 10:15

    Hi Christian,
    Great Blog! Great Xtext-Examples!
    I know, this blogpost is quite old, but as a Xtext-Newbie I’m still trying to get your example running in my project. I hope you can give me a hint to solve the following problem:

    I get a NullPointerException at this line (fileAccessProvider is null):
    final EclipseResourceFileSystemAccess fsa = fileAccessProvider.get();

    When I create a new blank Xtext project your example is working fine, but I need to use your example in my existing project. Why is the injected Provider null? Do I need to bind/configure/provide it in the MyDslUiModule?

    • July 18, 2013 at 10:19

      Hi make sure you use YourdslExecutableExtensionFactory in plugin.xml

      • Tommy Dvorczak
        July 18, 2013 at 10:35

        Thank you so much!!! I was searching for hours …

  10. Anshul Sharma
    August 1, 2013 at 08:26

    Hi Christian,
    Thanks for the blog.
    I am able to activate the context on my custom(.xform) file.
    The only problem i am facing is when i run eclipse as a product then the menu is somehow not activated
    Any pointers will be helpful.

    • August 1, 2013 at 08:57

      Sorry have no idea. This seem to be a general eclipse problem in your case and not a Xtext specific one

  11. September 9, 2013 at 19:35

    Hi Christian, great post!
    Thank you !

  12. davide
    October 17, 2013 at 10:06

    Hi Christian. I’m having some problems with your solution. I Replaced EclipseResourceFileSystemAccess with EclipseResourceFileSystemAccess2 (the first is deprecated), but the injection fails. Here

    final EclipseResourceFileSystemAccess2 fsa = fileAccessProvider.get();

    fileAccessProvider is still null. The injection also fails in generator field. What can i do?

    • October 17, 2013 at 10:14

      How do you register the handler in plugin.xml

      • davide
        October 21, 2013 at 08:02

        This way:

  13. davide
    October 21, 2013 at 08:03
  14. davide
    October 21, 2013 at 08:04

    Sorry i can’t paste code O_ò this is the pastebin link http://pastebin.com/KFW0agCG

  15. davide
    October 21, 2013 at 08:56

    Ok i solved. I figured out that the error was in class attribute of the handler section of the plugin.xml file. I needed to add ‘org.xtext.example.mydsl.ui.MyDslExecutableExtensionFactory:[class handler name]‘ to solve

    =D

  16. October 28, 2013 at 17:12

    I want to add context menu for two files with different extension.
    ex : test.rot, test2.tct, test4.dfg

    How to edit below line in the plugin.xml to include context menu for different files with different extension.

    value=”*.mydsl”/>

  17. October 29, 2013 at 16:38

    Thanks for your response. Let me check..

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: