Friday, December 5, 2014

EASE scripts conquer the UI

EASE just recently got extended to inject scripts into the UI. This means you may insert scripts into your view toolbars, menus and context menus. No need to deploy anything, just provide the script and add some keywords to it.

During this tutorial we will develop our script step by step. The final version is available from the EASE scripts repository.

To follow this tutorial you need to install EASE into your IDE/RCP.

Step 1: A very simple script

Our target will be to open a file browser on the current location selected in the Project Explorer view.

Create a General/Project in your workspace and a file Explore from here.js. To launch an external program we may use the Platform module:
loadModule('/System/Platform');
// runProcess('krusader');
runProcess('explorer.exe')
Now register your script location in the preferences: Scripting/Script Locations. Afterwards test your script by running it using one of the follwing methods:
  • double click in the Script Explorer view
  • right click in Project Explorer and select Run As/EASE Script
  • D&D script file to Script Shell view
Step 2: Bind to toolbar

As a next step we want to attach our script to the toolbar. So open your script file again and add following comment at the top of the file:
// ********************************************************************************
// name                 : Explore from here
// toolbar              : Project Explorer
// script-type          : JavaScript
// description          : Start a file browser using current selection.
// ********************************************************************************
The new toolbar button appears right after saving the script file. No need to restart eclipse! If not you probably did not set up your script locations in preferences correctly.
The toolbar keyword accepts view titles and view IDs if you know them. All supported keywords are listed in the wiki.


Step 3: Using the current selection

When starting the file browser we want to use the current selection as our start folder. it can be easily consumed by using getSelection() from the UI module:
loadModule('/System/UI');

var selection = getSelection();
if (selection instanceof org.eclipse.jface.viewers.IStructuredSelection)
 selection = selection.getFirstElement();

if (selection instanceof org.eclipse.core.resources.IFile)
 selection = selection.getParent();

if (selection instanceof org.eclipse.core.resources.IContainer)
// runProcess("/usr/bin/krusader", ["--left", getSystemProperty("user.home"), "--right", selection.getRawLocation()]);
 runProcess("explorer.exe", [selection.getRawLocation()]);
First we extract the very first element of the current selection. In case it is a file, we get its parent folder. Finally check that we really have a container, then open the file browser.


Step 4: Adding a context menu entry

We might also want to bind our script to any IResource context menu in our application. This is really simple, just add following keyword to the header:
// popup                : enableFor(org.eclipse.core.resources.IResource)

Step 5: Adding an image to the toolbar

Instead of having a text button in the Project Explorer toolbar we may replace it with an image. You guessed it, its just another keyword:
// image                : platform:/plugin/org.eclipse.ease.ui/icons/eobj16/folder.png
This URI reuses an icon stored within the ease.ui plugin. You may use relative paths, file system paths or even http URIs for your images.
When you start building up your own script library, consider pushing your scripts to our script sample repository.

Monday, November 10, 2014

Google Summer of Code - student experience

This year we received a great contribution from Martin, who worked on a GSoC project for EASE. Belows post sums up his experience with the GSoC program. So I leave the stage for Martin...




Hello everyone. First off let me quickly introduce myself. My name is Martin and I study software-engineering at Technical University Graz. This summer I participated in the Google Summer of Code program (GSOC) where I implemented a Jython debugger for the Eclipse Advanced Scripting Environment with the help of my mentor Christian who today is so friendly and "borrows" me his blog.

I wanted to write a blog entry for quite a while now but was never sure what to write about. I finally decided to simply tell my experience with this year's GSOC program and try to give a few pointers to anyone who is interested in participating next year. Please understand that these are just things that worked for me and there is no guaranty that you will be selected next if you do everything I did.

In general the most important part about applying is to have a pretty good plan what you are going to do. The application phase was mid of March this year and applicants where expected to submit a project proposal. This proposal should contain an outline of what you want to achieve, what your milestones and deliverables are and an estimation about your timeline. Based on this application your mentoring organization (the open source organization you are applying to) will choose who they take.

Eclipse has a list of projects for Google Summer of Code and so do several other organizations but if you think you have a potentially great idea just get in contact with them and they will likely tell you if this is something they are interested in.

Once you think you have an idea I would recommend getting in touch with the mentoring organization and see if they have anything particular in mind for your project or if they can give you some tips on how you could tackle the problem. I always like an open dialog about my projects and to hear what others think about it. It is hard to let go off a fixed plan in your head if you don’t get reasonable input from someone else. In my case my mentor helped me a lot in shaping the idea to an actual plan.

If you finally think you have a decent plan about what you want to do it is time to think about how you want to do it. This phase to me is the most important phase of the whole GSOC program. The better thought through your plan is the less work you’ll have to do in the actual implementation phase. Don’t get me wrong, you will still make amends as you go along but the better the initial plan the less derivations there will be and the less additional time you will spend planning.

Make a list of all deliverables (necessary and optional), order and rank them and create your deliverable plan. Once again the more time you spend here the less surprises you will face when doing the actual implementation. Should I even mention that you should give your planned timetable some thought and try to be as accurate as you can at this point of you project? I think you see where everything so far was going…

So one last time: Take your time when writing your project proposal, try to go into enough detail and do not just quickly hack together a proposal. It will help your chances of getting selected because you obviously show dedication and once you actually are taken it helps you bring your project to a satisfying end. The rest of the program – the actual implementation – is quite straight forward. I will still give you some last hints that helped me and hopefully help you too.

Communication is the most important part in the implementation phase. Try to align with your mentor regularly, try to get feedback from others working on your open source project (mailing lists, IRC, you name it) and try to communicate your progress with everyone involved. Your mentor can and will help you with your problems, that’s why they are mentors in the first place. I knew my mentor before so it is easy for me to say this but from what I’ve heard from others every mentor seems to be nice and willing to help you if you only ask. There are some shy people out there – which is totally okay – but they will probably have a harder time. As I said, I really like feedback and it helps me finish projects better than if I was working completely on my own. I know it is hard for software developers to change their minds once they have it set on an idea (believe me I’ve been there more often than I can count) but feedback will almost certainly help. Being more open minded about this is something I strongly believe most (including me) should always try to improve.

The other persons involved in your OS project will probably be the first ones to use your software and play the beta-testers. Listen to their feedback, try to be open to their ideas and wishes and by this you will get the most out of your project. For them as well as for you.

Lastly try to communicate your project’s progress regularly. This is something I could have done a little more and although it worked this time it is still something I would do differently in the future. Your mentor and everyone else involved will help you avoid the stress of having to work triple-shifts just to finish before the deadline because you did not see the stress coming. Most people working on an open source project have experience and have been in similar situations (be it at work or in private) and will help you anticipate these things.

For GSOC there are two deadlines, one in the middle of the implementation phase to show that you haven’t completely wasted the first one and a half months and one at the end where you have to deliver your final results.

I guess lots of people handle deadlines the way I used to do, which is procrastinating until it is almost too late. I understand that you work best under pressure and seek the thrill and challenge of having close to no time to do your job, but it is still something I try to avoid as much as possible (although it still happens from time to time). Your mentors will very likely give you a positive final evaluations if you do not complete everything but tried your best throughout the whole project. If you procrastinate and do not finish then I’m pretty sure you will fail your finals and (maybe even more important) will not get the payment.

These are all tips I can give you, maybe there are others out there that can help you even more. Still I believe if you keep these things in mind but still tackle the program in your way there will be nothing stopping you from participating and finishing next year. Believe me, it is worth it.

Have fun and who knows maybe we see each other during next year’s Google Summer of Code program. I will definitely try my luck again.

Tuesday, October 21, 2014

Writing modules for EASE

While EASE is shipped with some basic functionality already implemented, the real benefit comes when we provide our own script modules. Today we will see how such modules are provided.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.

Step 1: Get EASE

To write modules you either need to install EASE into your running IDE, provide a target platform containing EASE, or import the source (core repository is sufficient) directly. At the end of this tutorial we will create help pages automatically. If you want to use that feature directly go for the source code option.

If not sure which option to choose simply install EASE from the update site.

Step 2: A simple module

Modules are simple classes, that are registered using the org.eclipse.ease.modules extension point. Start with creating a new Plug-in Project and create the extension point. Create a new module extension with id, name and a class. visible sets the default module visibility, but can be changed by the user in the preferences anytime.

If you start providing more and more modules it is a good idea to cluster them by using categories. This results in a cleaner UI integration using trees instead of a flat list of modules. Categories are defined under the same extension point and are straight forward, so I will not explain in detail.


The implementation starts with a POJO class:

package com.codeandme.ease.modules;

import org.eclipse.ease.modules.WrapToScript;

/**
 * Provides basic mathematics operations.
 */
public class SimpleMathModule {

 /**
  * Provide sum of 2 variables.
  *
  * @param a
  *            summand 1
  * @param b
  *            summand 2
  * @return sum
  */
 @WrapToScript
 public double sum(double a, double b) {
  return a + b;
 }

 /**
  * Subtract b from a.
  */
 @WrapToScript
 public double sub(double a, double b) {
  return a - b;
 }

 /**
  * Multiply 2 values.
  */
 @WrapToScript
 public double mul(double a, double b) {
  return a * b;
 }

 /**
  * Divide a by b.
  */
 @WrapToScript
 public double div(double a, double b) {
  return a / b;
 }

 public void doNothing() {
  // not exposed as it lacks the @WrapToScript annotation
 }
}

The only addition to a plain class is the @WrapToScript annotation, which indicates methods to be exposed to scripting. In case a class does not contain any @WrapToScript annotation, all public methods will get exposed.
As modules are created dynamically, make sure that the default constructor exists and is visible!

Step 3: A more complex module

Our 2nd module will be a bit more complex as we introduce a dependency to the SimpleMathModule. Define the module the same way we did before, but add a dependency to com.codeandme.ease.modules.module.simpleMath.


The implementation now derives from AbstractScriptModule which allows us to retrieve the used script engine and the basic environment module, which keeps track of loaded modules. If you cannot extend your class you may also implement IScriptModule and populate the initialize() method on your own.

package com.codeandme.ease.modules;

import org.eclipse.ease.modules.AbstractScriptModule;
import org.eclipse.ease.modules.WrapToScript;

/**
 * High sophisticated mathematics operations.
 */
public class ComplexMathModule extends AbstractScriptModule {

 /** PI constant. */
 @WrapToScript
 public static final double PI = 3.1415926;

 /**
  * Get radus from circle area.
  *
  * @param area
  *            circle area
  * @return radius
  */
 @WrapToScript
 public double getRadius(double area) {
  double rSquare = getEnvironment().getModule(SimpleMathModule.class).div(area, PI);
  return Math.sqrt(rSquare);
 }
}

By querying the environment we can retrieve other module instances dynamically and make use of their functionality.

We also expose a constant PI here. Just remember that only constant fields can be wrapped.


Optional: Create documentation

Users typically will request for module documentation. EASE allows to create eclipse help pages automatically by using a special JavaDoc doclet. The doclet is available in source and binary from the EASE core repository. To use it you need to download at least the bin folder content (along with the directory structure).

Now open menu Project / Generate JavaDoc...  , provide the location of the javadoc binary, select your modules project and point to the custom doclet location.



On the 2nd page add a parameter that points to the Plug-in Project root folder:

-root /your/workspace/location/com.codeandme.ease.modules


Hit Finish to build the documentation. The doclet will alter your plugin.xml, MANIFEST.MF and adds a new help folder to your project.

The help pages are located under Scripting Guide/Loadable modules reference/<Category>/<ModuleName>.

If you are interested in building documentation automatically using maven, please ask on the ease-dev mailing list for details.

Thursday, October 16, 2014

EASE @ EclipseCon Europe

 
Just 10 days until EclipseCon Europe opens its doors. If you did not register yet, it is about time! All these tutorials and talks along with meeting the developers behind those tools is inspiring and well worth the costs.

While there will be no official talk about EASE this year, you may join our Where to go with EASE session at the Unconference Day on monday.

Catch me for a coffee break or meet me at the hackathon and discuss your scripting needs with me.

Friday, October 10, 2014

Eclipse Stammtisch Graz, Austria

No tutorial today, instead I would like to invite you to the first

Eclipse Stammtisch Graz


Date: Wednesday, November 12th, 6pm
Location: Brot und Spiele

If you would like to attend please join the eclipse-stammtisch-graz mailing list to get further updates.

It is all about forging a community, right?

Thursday, August 21, 2014

EASE - Eclipse Advanced Scripting Environment launched

It's been a while since I last wrote about EASE. Not writing about it does not mean that we were lazy. We applied for an official eclipse project and were busy setting up the infrastructure.

So please welcome the new Eclipse citizen: EASE.


What it does for you

EASE allows to write, maintain and execute scripts right within your IDE. Executed scripts run in the context of the Eclipse JRE, allowing to access all Java classes in your Eclipse universe. Thus you can manipulate and extend your IDE without the need to write plugins, pack them into features, export them into a p2 repository, install, restart, ...

As accessing Java code from script languages is typically an annoying task ("If I could write Java code I would do it in Java, why scripting?") EASE provides extension points to encapsulate typical actions into simple script commands. Basically it allows to create wrappers in the target script language to access Java methods. We already started writing some useful modules. The Modules Explorer view gives a short overview of the available commands (hint: try DND).


You already have a nice API to use? Great, just wrap() it from your script or register it via extension point.

Scripts may include other scripts using URIs. You could even access your scripts using http.To register script locations check your preferences in Preferences/Scripting.

Current UI integration gives access to a nice interactive shell to play around with, script recording and launch support.



Right, where do I get it from?

You find the update site locations on our webpage. We are focusing on Eclipse Luna, so if you are using something older and things do not work as expected, let us know.


Contribute? It's easier than you thought

Contribution is not only about writing code. Just test EASE and let us know what you think of it. Fill the bugtracker with ideas, design icons, write help, provide sample scripts - there are so many things that need a little care.


What's next

This summer we received a great contribution via Google Summer of Code. Martin Kloesch developed a Jython debugger which will be available on the update sites soon.

UI integration to bind scripts to menus and toolbars is basically working, but needs some tweaking.

A set of core modules needs to evolve. Currently we are playing around with the functions, but we need a stable script API for those modules rather soon.

... and I know we have to write lots of help and tutorials to make this project useful for you out there.

Thursday, June 19, 2014

TableViewer context menus

Recently I played around with context menus on tableviewers. I wanted to track the column where the context menu was activated. As it turned out to be quite a tricky task (until you know how it is done) I would like to share my findings. Furthermore I would like to show how to attach context menus to editors without having default entries for Run As and similar actions.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.

Preparations

For this tutorial I created a Plug-in Project with a simple editor extension. The editor contains a TableViewer with some sample columns. Nothing special about that. Have a look at the source code or Lars' excellent editor tutorial if you are not familiar with these steps.

Step 1: Creating the editor context menu

To enable context menus, we need to register them in our editor code. The typical snippet to do this looks as follows:
MenuManager menuManager = new MenuManager();
Menu contextMenu = menuManager.createContextMenu(table);
table.setMenu(contextMenu);
getSite().registerContextMenu(menuManager, tableViewer);
registerContextMenu() automatically searches for a menu contribution with locationURI = popup:<part.id>. In our case it would look for popup:com.codeandme.editor.sample. You may also provide a dedicated id when registering the context menu. Just make sure you provide the id without the "popup:" prefix, while the menu contribution needs the "popup:" prefix in its locationURI.

You will end up with a context menu already populated with some default entries like Run As, Compare With, Team, and some more. To get rid of them, we need to register the menu using a different site:
getEditorSite().registerContextMenu(menuManager, tableViewer, false);
The boolean parameter allows to enable/disable context menu entries for the editor input part.


Step 2: Track the active column

When we want our context menu entry to behave differently depending on the column from where it was triggered, we need to track columns. Some solutions on the web use MouseListeners, which work well for the table body, but not for the header row. A nicer solution relies on MenuDetect events:
fTableViewer.getTable().addListener(SWT.MenuDetect, this);

@Override
public void handleEvent(Event event) {
 Table table = fTableViewer.getTable();

 // calculate click offset within table area
 Point point = Display.getDefault().map(null, table, new Point(event.x, event.y));
 Rectangle clientArea = table.getClientArea();
 fHeaderArea = (clientArea.y <= point.y) && (point.y < (clientArea.y + table.getHeaderHeight()));

 ViewerCell cell = fTableViewer.getCell(point);
 if (cell != null)
  fSelectedColumnIndex = cell.getColumnIndex();

 else {
  // no cell detected, click on header
  int xOffset = point.x;
  int columnIndex = 0;
  int[] order = table.getColumnOrder();
  while ((columnIndex < table.getColumnCount()) && (xOffset > table.getColumn(order[columnIndex]).getWidth())) {
   xOffset -= table.getColumn(order[columnIndex]).getWidth();
   columnIndex++;
  }

  fSelectedColumnIndex = (columnIndex < table.getColumnCount()) ? order[columnIndex] : NO_COLUMN;
 }
}
The full helper class is available under EPL and can be downloaded from the source repository.

Step 2: Delete the active column

To provide a usage example for the TableColumnTracker we will extend our editor and allow users to delete the column under the cursor using the context menu.

The command implementation simply asks the current editor to do the job:
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
 IWorkbenchPart part = HandlerUtil.getActivePart(event);
 if (part instanceof SampleEditor)
  ((SampleEditor) part).deleteColumn();

 return null;
}
The editor needs to install the tracker and dispose the selected column upon request:
public class SampleEditor extends EditorPart {

 private TableColumnTracker fColumnTracker;

 @Override
 public void createPartControl(Composite parent) {

  [...]

  MenuManager menuManager = new MenuManager();
  Menu contextMenu = menuManager.createContextMenu(table);
  table.setMenu(contextMenu);
  getEditorSite().registerContextMenu(menuManager, fTableViewer, false);

  fColumnTracker = new TableColumnTracker(fTableViewer);
 }

 public void deleteColumn() {
  int columnIndex = fColumnTracker.getSelectedColumnIndex();

  if (columnIndex != TableColumnTracker.NO_COLUMN) {
   fTableViewer.getTable().getColumn(columnIndex).dispose();
   fTableViewer.refresh();
  }
 }
}

Wednesday, June 11, 2014

Adding hyperlink detectors to editors

Ever tried clicking on a method name in the java editor while holding the ctrl key? Sure you have. This hyperlink functionality is extensible and in this post we will see how to do that.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.

Step 1: Creating the extension

Our target will be to create a simple hyperlink whenever we detect the word "preferences" in a text editor. Upon a click the preferences dialog should pop up.

Start with a new Plug-in Project, open the Manifest Editor and switch to the Extensions tab. Now add an org.eclipse.ui.workbench.texteditor.hyperlinkDetectors extension. Provide a unique id and a nice name. The name will be visible in the preferences under General/Editors/Text Editors/Hyperlinking.

The targetId points to the type of editor we would like to create our links in. As we want to use it in all text editors use org.eclipse.ui.DefaultTextEditor here.


Step 2: Class implementation

Create a new class PreferencesHyperlinkEditor extending from AbstractHyperlinkDetector. Therefore you need to define a dependency to the org.eclipse.jface.text plug-in.
package com.codeandme.hyperlinkdetector;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;

public class PreferencesHyperlinkDetector extends AbstractHyperlinkDetector implements IHyperlinkDetector {

 private static final String PREFERENCES = "preferences";

 @Override
 public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {

  IDocument document = textViewer.getDocument();
  int offset = region.getOffset();

  // extract relevant characters
  IRegion lineRegion;
  String candidate;
  try {
   lineRegion = document.getLineInformationOfOffset(offset);
   candidate = document.get(lineRegion.getOffset(), lineRegion.getLength());
  } catch (BadLocationException ex) {
   return null;
  }

  // look for keyword
  int index = candidate.indexOf(PREFERENCES);
  if (index != -1) {

   // detect region containing keyword
   IRegion targetRegion = new Region(lineRegion.getOffset() + index, PREFERENCES.length());
   if ((targetRegion.getOffset() <= offset) && ((targetRegion.getOffset() + targetRegion.getLength()) > offset))
    // create link
    return new IHyperlink[] { new PreferencesHyperlink(targetRegion) };
  }

  return null;
 }
}
The sample implementation just extracts some text and calculates offsets within the text file. It will fail if you type"preferences" more than once per line, but as a proof of concept this should be sufficient.

We also need to provide an implementation of IHyperlink. The most important method there is open(), which is called upon the click event.
package com.codeandme.hyperlinkdetector;

import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.dialogs.PreferencesUtil;

public class PreferencesHyperlink implements IHyperlink {

 private final IRegion fUrlRegion;

 public PreferencesHyperlink(IRegion urlRegion) {
  fUrlRegion = urlRegion;
 }

 @Override
 public IRegion getHyperlinkRegion() {
  return fUrlRegion;
 }

 @Override
 public String getTypeLabel() {
  return null;
 }

 @Override
 public String getHyperlinkText() {
  return null;
 }

 @Override
 public void open() {
  PreferencesUtil.createPreferenceDialogOn(Display.getDefault().getActiveShell(), null, null, null).open();
 }
}
To test your implementation open a new text file, enter some sample text and hover over the word "preferences" while holding the ctrl key.

Wednesday, June 4, 2014

Tycho 11: Install root level features


Introduction

Do you know about root level features?

Components installed in eclipse are called installable units (IUs). These are either features or products. Now IUs might be containers for other features, creating a tree like dependency structure. Lets take a short look at the Installation Details (menu Help / About Eclipse) of our sample product from tycho tutorial 8:

We can see that there exists one root level feature Tycho Built Product which contains all the features we defined for our product. What is interesting is, that the Update... and Uninstall... buttons at the bottom are disabled when we select child features.

So in an RCP application we may only update/uninstall root level features. This means that if we want to update a sub component, we need to create a new version of our main product. For a modular application this might not be a desired behavior.

The situation changes when a user installs additional components into a running RCP application. Such features will be handled as root level features and can therefore be updated separately. So our target will be to create a base product and install our features in an additional step.

Great news is, that tycho 0.20.0 allows us to do this very easily.

Tycho Tutorials

For a list of all tycho related tutorials see Tycho Tutorials Overview

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online.

Step 1: Identify independent features

Tycho will do all the required steps for us, we only need to identify features to be installed at root level. So open your product file using either the Text Editor or XML Editor. Locate the section with the feature definitions. Now add an installMode="root" attribute to any feature to be installed on root level.
   <features>
      <feature id="org.eclipse.e4.rcp"/>
      <feature id="org.eclipse.platform"/>
      <feature id="com.codeandme.tycho.plugin.feature" installMode="root"/>
      <feature id="com.codeandme.tycho.product.feature"/>
      <feature id="org.eclipse.help" installMode="root"/>
      <feature id="org.eclipse.emf.ecore"/>
      <feature id="org.eclipse.equinox.p2.core.feature"/>
      <feature id="org.eclipse.emf.common"/>
      <feature id="org.eclipse.equinox.p2.rcp.feature"/>
      <feature id="org.eclipse.equinox.p2.user.ui"/>
      <feature id="org.eclipse.rcp"/>
      <feature id="org.eclipse.equinox.p2.extras.feature"/>
   </features>

Make sure to update the tycho version to be used to 0.20.0 or above.

Nothing more to do, build your  product and enjoy root level features in action.


Sunday, May 25, 2014

Implementing a custom discovery site

When developing your own components you might end up with some optional features you do not want to install by default. Typically your first option would be to put additional features to an update site so your users can install them on their own.

But sometimes it would be great to use a more polished interface like the discovery mechanism used by maven or subversive. As p2 already provides all the necessary dialogs such a feature can easily be implemented in your own application.

There exist two predefined commands for the discovery wizard. This tutorial will describe both of them.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.  

Option 1: Display p2 repository content in a wizard

The first option allows to simply display the content of an existing p2 repository in a nicer way. To use it, simply add a new command to a toolbar or menu. Use org.eclipse.equinox.p2.ui.discovery.commands.ShowRepositoryCatalog as commandId and add a parameter to it. Set the parameter name to org.eclipse.equinox.p2.ui.discovery.commands.RepositoryParameter and the value to the URI of the p2 update site to use.

 That's it, nothing else to do. After activating the command you will end up with a dialog like this:

While this solution is extremely simple, it has some drawbacks:
  • only one p2 repository per handler
  • no filtering
  • no extended information (icons, links, ...)

Option 2: Customize dialog with a discovery site

While the first option might be sufficient for small repositories, you might want to have more control over the displayed items when your components get more complex. The second mechanism allows you to exactly define the content of the wizard.

Step 1: Add command

As before we can use a predefined command with using commandId org.eclipse.equinox.p2.ui.discovery.commands.ShowBundleCatalog. Again we need a parameter: set name to org.eclipse.equinox.p2.ui.discovery.commands.DirectoryParameter. For value you need to provide a URL that points to an XML file containing directory information. In the example code we will host this locally. In a real life scenario you would put this on your project website.

Step 2: Populating directory.xml

A directory is a simple list of eclipse plugin files (in jar format) that contain further information.

<?xml version="1.0" encoding="UTF-8"?>
<directory>
 <entry
  url="file:///mnt/data/develop/workspaces/Blog/com.codeandme.discovery/resources/directory.jar"
  permitCategories="true" />
</directory>

Each entry points to an eclipse plugin that contains actual extension listings.

Step 3: Provide extension listings

Create a new Plug-in Project and switch to the Extensions tab of your plugin.xml. Add a new extension of type org.eclipse.mylyn.discovery.core.connectorDiscovery.

The first component to create is a connectorCategory. It will show up as a nice blueish bar in the wizard. The values to provide are pretty much self explanatory. Categories may have icons and an overview. The overview will be denoted as a small info icon on the right hand side of the entry. It will open a popup on mouse hover.

Now we may add dedicated components to a category. Therefore add a new categoryDescriptor to the extension point. Add all the required fields which should be straight forward. Similar to the category description before we may set an icon and overview information. The id provides a feature identifier of the component to be installed. If we want to install multiple features at the same time, we can attach iu (installable units) nodes to the descriptor. By providing such nodes, the original id will not be used anymore, so make sure you add that feature as a iu node, too.

Optional you may add a certification node to the extension point. If present, a categoryDescriptor may link to it, which provides a "certified" link at the end of its description.


Make sure you deploy the plug-in to the correct location as defined in the directory.xml file and give it a try.

Note

When using the example code from svn you have to update directory.xml content to fit your local path. Also update the location of directory.xml in your command parameter.

Thursday, May 15, 2014

Extending JSDT: adding your own content assist

Extending the JSDT content assist is a  topic asked several times on stackoverflow(20738788, 20779899). As I needed it for the Eclipse EASE project, I decided to come up with a short tutorial.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly. 

Step 1: Preparations

As we will have dependencies to JSDT you either need to check out the source code from gerrit or add JSDT to your target platform.

Step 2: Defining the extension point

Create a new Plug-in Project com.codeandme.jsdt.contentassist and add following dependencies:
  • org.eclipse.wst.jsdt.ui
  • org.eclipse.core.runtime
  • org.eclipse.jface.text
Afterwards create a new Extension for org.eclipse.wst.jsdt.ui.javaCompletionProposalComputer. Provide an ID and Name. The ID will be needed as reference to a category, the name will be displayed in Preferences/JavaScript/Editor/Content Assist/Advanced. Create a proposalCategory node and provide a nice icon for your category.


Create a second extension of the same type for our implementation. Again provide an ID. Create a javaCompletionProposalComputer subnode with a class and activate set to true. The categoryID consists of your plugin name followed by the category ID we provided earlier.


If you use a target definition containing JSDT plugins you might not be able to use the Plug-in Manifest Editor for creating these extensions. In that case you have to edit the xml code directly:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension point="org.eclipse.wst.jsdt.ui.javaCompletionProposalComputer"
   id="codeandme_category"
   name="Code and me proposals">
   <proposalCategory/>
 </extension>

 <extension
       id="codeandme_proposal"
       point="org.eclipse.wst.jsdt.ui.javaCompletionProposalComputer">
   <javaCompletionProposalComputer
      class="com.codeandme.jsdt.contentassist.CustomCompletionProposalComputer"
      categoryId="com.codeandme.jsdt.contentassist.codeandme_category"
      activate="true">
   </javaCompletionProposalComputer>
 </extension>

</plugin>
Step 3: Proposal computer implementation

Now for the easy part:create a new class CustomCompletionProposalComputer with following content:

package com.codeandme.jsdt.contentassist;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.wst.jsdt.ui.text.java.ContentAssistInvocationContext;
import org.eclipse.wst.jsdt.ui.text.java.IJavaCompletionProposalComputer;

public class CustomCompletionProposalComputer implements IJavaCompletionProposalComputer {

 @Override
 public void sessionStarted() {
 }

 @Override
 public List computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) {

  ArrayList<CompletionProposal> proposals = new ArrayList<CompletionProposal>();

  proposals.add(new CompletionProposal("codeandme.blogspot.com", context.getInvocationOffset(), 0, "codeandme.blogspot.com".length()));
  proposals.add(new CompletionProposal("<your proposal here>", context.getInvocationOffset(), 0, "<your proposal here>".length()));

  return proposals;
 }

 @Override
 public List computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) {
  return null;
 }

 @Override
 public String getErrorMessage() {
  return null;
 }

 @Override
 public void sessionEnded() {
 }
}



CompletionProposals could contain more information like attached documentation or ContextInformation for variables and other dynamic content. See this post for some information on the Contextinformation.

Optional: Helper methods

Typically you would need to evaluate the last characters before the cursor position to filter your proposals. You also might be interested in the content of the current line left of the cursor position. Maybe these helper methods are of some interest:
 private static final Pattern LINE_DATA_PATTERN = Pattern.compile(".*?([^\\p{Alnum}]?)(\\p{Alnum}*)$");

 /**
  * Extract context relevant information from current line. The returned matcher locates the last alphanumeric word in the line and an optional non
  * alphanumeric character right before that word. result.group(1) contains the last non-alphanumeric token (eg a dot, brackets, arithmetic operators, ...),
  * result.group(2) contains the alphanumeric text. This text can be used to filter content assist proposals.
  * 
  * @param context
  *            content assist context
  * @return matcher containing content assist information
  * @throws BadLocationException
  */
 protected Matcher matchLastToken(final ContentAssistInvocationContext context) throws BadLocationException {
  String data = getCurrentLine(context);
  return LINE_DATA_PATTERN.matcher(data);
 }

 /**
  * Extract text from current line up to the cursor position
  * 
  * @param context
  *            content assist context
  * @return current line data
  * @throws BadLocationException
  */
 protected String getCurrentLine(final ContentAssistInvocationContext context) throws BadLocationException {
  IDocument document = context.getDocument();
  int lineNumber = document.getLineOfOffset(context.getInvocationOffset());
  IRegion lineInformation = document.getLineInformation(lineNumber);

  return document.get(lineInformation.getOffset(), context.getInvocationOffset() - lineInformation.getOffset());
 }