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 | #1

    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 | #2

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

      regards,
      Markus

  2. Markus
    November 16, 2011 at 14:17 | #3

    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 | #4

      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 | #5

      Hi,

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

      ~Christian

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

    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 | #7

      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 | #8

    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 | #9

      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 | #10

    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 | #11

      Hi did you do what i suggested regarding IProgressMonitor and IFileCallback

      • Markus
        July 6, 2012 at 08:19 | #12

        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 | #13

    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

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

    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 | #16

      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 | #17

        Thank you, Christian!

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

    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 | #19

      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 | #20

        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 | #21

    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 | #22

      Hi make sure you use YourdslExecutableExtensionFactory in plugin.xml

      • Tommy Dvorczak
        July 18, 2013 at 10:35 | #23

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

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

    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 | #25

      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 | #26

    Hi Christian, great post!
    Thank you !

  12. davide
    October 17, 2013 at 10:06 | #27

    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?

  13. davide
    October 21, 2013 at 08:03 | #30
  14. davide
    October 21, 2013 at 08:04 | #31

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

  15. davide
    October 21, 2013 at 08:56 | #32

    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 | #33

    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 | #35

    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: