Thursday, August 27, 2009

Pythonisque indent/dedent block structure for a Xtext DSL


Xtext is a framework for development of textual domain specific languages (DSLs). You need to describe the DSL using Xtext's simple grammar and Xtext generates a parser, an editor and other goodies. You are on the way to create your own IDE for the DSL.
Xtext, out of the box, does not support using white spaces for creating block structure. With a small hack, you can make Xtext understand block structure defined using white space.
The basic idea is to replace Xtext's lexer with a custom lexer that adds the INDENT and DEDENT tokens into the parser.
My original answer in to a post on Xtext news group gives the generic procedure. However some points need to be modified.
Overriding bindLexer() does not really work. Xtext creates a concrete Lexer object in the parser. So for providing a CharStream, we need to bindIAntlrParser() and the in the parser class create the lexer that creates the CharStream.
You can access a sample project that implements this from google code. Use svn to checkout org.xtext.example.mydsl and org.xtext.example.mydsl.ui projects from http://eclipse-snippets.googlecode.com/svn/trunk/xtext-indenting.

Monday, July 28, 2008

ResourceSelectionDialog: adding filter capabilities

When working on plugins once in a while we hit a wall. We need to implement some functionality, it already exists in eclipse in some form - but it is not exactly what we want. For example, when you want a user to select some resources from the workspace - ResourceSelectionDialog provides the functionality needed. Unfortunately, RSD does not provide a way for filtering the resources that can be chosen. RSD displays *all* the resources from a given root (some IContainer - workspace root, project or a folder) - there is no way to tell RSD that we want it to filter some resources.

When we are stuck with this situation, we have the following solutions:

  1. Copy the code (in this case RSD) and modify it to our purposes. A major issue with this approach is that not only ResourceSelectionDialog.java, but you need to also copy all other classes that are used by RSD that are not made public by the plugin. Depending on the case these might be few or a lot.
  2. Another approach is to derive a class from RSD (though the documentation says that we should not) and implement the required functionality. In the case of RSD, we need to override the createDialogArea because the getResourceProvider is private and we need to add functionality into the getResourceProvider.
  3. Create a patch to RSD and submit to eclipse and hope that it will get included. Seeing that this request is made almost 3 years back (see bug 91227 and bug 124338)  and still there is not much of movement, you must be very lucky for this to get included and be ready for your plugin release.
  4. The one option I want to discuss in this post is to bend the RSD to work your way. GoF has a nice little pattern for this - they call it adapter.
From the 'Design Patterns' book:

An adpater 
Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
We use a variant of adapter pattern to make RSD to accept resources that also implement filtering.

What we need is a parallel hierarchy of classes for IResource - folder, project and file that wraps a real IFolder, IFile, IProject and also provides filtering. We create classes FileDelegate, FolderDelegate and ProjectDelegate and use eclipse's source -> create delegate methods to delegate all the functions wrapped object, except for those methods that return a IResource(s). Each of the methods that return a IResource(s) uses the delegate to get the returned IResource(s)  and creates the appropriate delegate object(s) and returns it. We wrap RSD also into another class and allow the user to set a validator onto this class. Each of the resource delegates gets a reference to this class and uses the validation from this class. We also need a little cleanup so that when searching for the resources we do not go higher than the root object that is passed to the RSD.

You can access the related code from Google code eclipse-snippets project.



Tuesday, July 15, 2008

A categorized content assist processor

Content assist is one of the nice features of Eclipse text editors. But unfortunately, we can add only a single ContentAssistProcessor at a time to the ContentAssistant for a given content type. Still, JDT does a good job of showing multiple categories of content assist in Java editor.

I posted an enhancement to eclipse bugzilla, that will allow editors to support multiple categories of content assist processors. I hope it will make to eclipse sometime, meanwhile you can pick up the code from the bug entry.

Monday, June 30, 2008

Programmatically opening an editor

Eclipse uses file associations for opening appropriate editor on a file. You can see this in action when you click on a Java file or a ant build file. The correct editor is opened for you. If you want to do the same thing in your own plugins - it could not be easier in 3.4.

The class org.eclipse.ui.IDE has a set of static functions that opens an editor and returns a handle to that editor. This function needs a IWorkbenchPage. We can typically get it by PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(). The second parameter is a handle to the file. It can be a reference to IFile, URI, IMarker, IEditorInput, IFileStore or a URI. The last two cases typically open editors for the files that are outside the file system.

I came across this gem when I need to open an editor on a OS file path. The file is in the workspace, but the path is OS specific. In this case I used ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(Path.fromOSString(file)) to convert the OS path into an IFile. The getFileForLocation() returns null in case the path does not belong to the workspace. That is OK - that is what I exactly wanted.

So here is the entire code:


String filePath = "..." ;
final IFile inputFile = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(Path.fromOSString(filePath));
if (inputFile != null) {
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
IEditorPart openEditor = IDE.openEditor(page, inputFile);
}


If you want to position the cursor to a specific line in the editor - do the following.


int Line = ...
if (openEditor instanceof ITextEditor) {
ITextEditor textEditor = (ITextEditor) openEditor ;
IDocument document= textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
textEditor.selectAndReveal(document.getLineOffset(line - 1), document.getLineLength(line-1);
}

Monday, July 23, 2007

QuickFix: Converting an Eclipse Preference Page to a View

One of the handy, time-saving and beutiful features of eclipse is the templates. You use CTL-Space (Cmd-Space on Mac) for invoking content assist that shows the currently defined templates. But most users do not bother to update the templates because the option is buried in preferences. For adding new templates to the java editor or modifying existing templates - you need to go through Preferences -> Java -> Editor -> Templates.;div>
Having the templates handy as a view will help in heavier use of templates. If you are interested in this update please vote for this enhancement at Eclipse Bugzilla.

Now for the quick fix.
Fortunately, both the viewpart and preferencepage are similar. A small plugin can convert the preference page to a view. What I have done is to take eclipse example view plugin (called SampleView). Remove all the code leaving the definition for the createPartControl() and setFocus(). Add this code in the createPartControl():

public void createPartControl(Composite parent) {
PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(
parent.getShell(),
"org.eclipse.jdt.ui.preferences.JavaTemplatePreferencePage",
new String[] {}, null);
PreferencePage selectedPage =
(PreferencePage) dialog.getSelectedPage();
selectedPage.createControl(parent);
}

Create the plugin and deploy it. You have the view that shows the template preference page (see the image below).


From this view, you can add/edit the templates.

Incidentally, this trick should work for any preference page that is derived from PreferencePage class.

Thursday, July 12, 2007

Tip: Creating and Sharing Launch Configurations

This happened again. I downloaded a Java product from sourceforge. It came with a .project, so it is easy enough to import it into the eclipse workspace. Now, comes the humdinger of the problem. How in the world should I launch the application. I can also see a tests package, how can I launch the tests?

Fortunately, eclipse solves this problem through launch configurations. If you are developing on eclipse there is no reason for not sharing the launch configurations along with the code. A launch configuration is a way to inform a fellow developer how to invoke the application. It is like providing a helping hand in during the initial stages - till he/she grows up and find how to launch the application, tests or whatever by themselves.

Temporary launch configurations

If you rightclick on a test case or a main class and used 'Run as...', eclipse creates a launch configuration and invokes the application for you. While developing an application (if you are like me) - you will accumulate a lot of launch configurations. These launch configurations are saved along with the workspace and not shared.

Creating a launch configuration

For creating a launch configuration start with a temporary launch configuration. Open the Run -> Run dialog... option from the eclipse menu.

Change the launch configuration name. Use a name that includes atleast the project name and the type of launch it is. No one can understand what 'Main' or 'AllTests' stand for. It is easy to understand 'SampleApp - Main' or 'SampleApp - AllTests'.

You can set the arguments for the VM as well as application from the (surprise!) Arguments tab. As far as possible parameterize the arguments. Do not ever use hard coded file names or directory names in a launch configuration that is shared. Eclipse has predefined variables 'file_prompt', 'folder_prompt' and 'string_prompt' for this purpose. When a launch configuration with such parameters is launched, Eclipse prompt the user to either select a file/folder or enter a string.

If your launch is dependent on a particular Java version (suppose you need atleast JDK 1.5 to work) - use the JRE tab to select the JRE. It is advisable to select a particular 'Execution environment' rather than an installed JRE.

The rest of the tabs are self explanatory. If you added any environment variables, remember to parameterize them wherever needed. Launch the configuration using the 'Run' option in the dialog and check everything works fine.

Sharing a launch configuration

If you want to share a launch configuration, you do it through the 'Run dialog'. For opening the run dialog use Run -> Run dialog... option from the eclipse menu. The sharing option is in the 'Common' tab of the run dialog.

Select the 'Shared file' option. Select the project to which this launch configuration belongs. I suggest the launch configurations to be saved at the root of the project directory. You can also add the launch configuration to the favorites menu (either to Run or Debug). Just click on 'Apply' and the launch configuration is saved. From now onwards, anyone who imports your project can launch the application by just clicking on the launch configuration file and selecting Run as -> <launch name&rt;.

Launch configuration best practices

  • Provide launch configurations for all modes of launch. For example, if your application can be launched in UI and command line mode, provide two launch configurations one for each.
  • Do not proliferate the project with temporary launch configurations. I have a separate project where I save all of my temporary launch configurations. This will be checked into the SCM, but not shared along with the project.
  • Provide a launch configuration for running all the tests (if exists).
  • Do not add optional launch configurations into the favorites. My suggestion is to add only the application and all tests into the favorite menu.
  • Parameterize the launch configurations using eclipse variables - folder_prompt, file_prompt and string_prompt.
  • Select an appropriate JRE using the JRE tab and an execution environment.

Finally, launch configurations are for fellow developers and not for end users. Keep it in mind when you create a configuration. Too much of hand-holding might not be needed.

Tuesday, July 10, 2007

Roll your own eclipse to avoid multiple downloads

Eclipse distribution weighs at a whopping 130MB for each platform. Like me if you want eclipse for multiple platforms - you may want to roll your own distribution using RCP delta pack along with eclipse downloaded to a single system. Here are the steps.

1. Download eclipse (I downloaded for OSX)
2. Download the RCP delta pack and install it into the eclipse distribution.
3. Run eclipse with a clean workspace. Alternatively you can switch workspace from the File menu of eclipse.
4. Create a plugin project (the name does not matter - I called it eclipse)
        a. Uncheck the 'create java project' under project settings
5. Create a new product configuration with the following settings:
        a. Name = eclipse
        b. Product ID = select new and create eclipse.product
        c. select based on features
        d. Under configuration tab, select 'Add all' and select all the features displayed.
6. Select plugin.xml and under build tab select plugin.xml to be included into the binary build.
7. From product configuration select export product.
          a. Select 'Export for multiple platforms' under Export options
          b. click 'Next' and select the platforms that you need.

At the end of these steps you will have eclipse for all the selected platforms. From your installation copy the config.ini (eclipse/configuration folder) and eclipse.ini (eclipse folder or eclipse/Eclipse.app/Contents/MacOS folder on OSX) to each of the target eclipses.

That is all. Now you have eclipse for all the platforms you need and you have saved some bandwidth and made net a better place by reducing the choking of the wires :-).