Extending Eclipse with Helpful Views

s a developer, one of the most important tools at your disposal is your IDE. As a Java developer, there are several IDEs available?both commercial and open source?that are each interesting in their own way and generally extendable. IDE extensions are so valuable because they allow you to create new functionality not offered by the base IDE.

In this article, I will explain how to extend one such IDE, Eclipse, using its plugin architecture. The Eclipse API is vast and can hardly be covered in a single book much less an article, so this article will focus on one specific concept known as views. I will cover creating two views that enable a developer to evaluate expressions in real time. The first view will evaluate a regular expression (RegEx) against arbitrary text and display the results. The second view will evaluate an XPath against an XML document and display the results.

In Eclipse, your main window is known as the workbench. The workbench provides a menu bar, tool bar, and a specialized view known as a perspective. A perspective is simply a collection of views organized in a layout. Each of these views provides a unique set of functionality, e.g. the Navigator view provides a tree-based view of a project’s directory allowing manipulation of files. There are many different perspectives in Eclipse, each with a different set of views associated with them. However, any perspective can be customized by the user through the addition and removal of views. The views that you create in this article will simply be added to the choice of views provided to the user.

Creating a View in Three Easy Steps
Creating a new view in Eclipse is a three-step process:

  1. Create a plugin to encapsulate the view.
  2. Declare in your plugin manifest that you wish to add a view extension.
  3. Create a class that implements your view.

Step 1: Creating a Plugin
Eclipse plugins allow for the arbitrary extension of Eclipse and as such can be very simple or very complex. Creating a plugin that extends Eclipse with a new view is actually very simple, so I am going to gloss over the plugin details and only cover what is needed. In fact, the Eclipse Plugin Development Environment (PDE) has a wizard that will take care of most of the effort. If you have never created a plugin in Eclipse or you are interested in the details, I recommend reading “Your First Plugin” by Jim Amsden.

To get started, create a new plugin project, using the wizard (see Figure 1).


Figure 1. Page One: The first page of the wizard to create a plugin project is straightforward.
 
Figure 2. Page Two: On the second page, be sure to give the wizard a unique ID number for the plugin.

Figure 3. The Plugin Editor: The plugin you just created with the wizard will be opened with the plugin editor.

On the second page, pay close attention to the plugin ID as the value must be unique across all plugins included in your Eclipse installation (see Figure 2).

After you have filled out the second page of the wizard, complete it by pressing the finish button. There is no need to continue on to the next page. When you complete the wizard, you will be asked whether to switch to the Plugin Development perspective. I recommend using this perspective, but the choice is yours. Whatever you chose, the plugin you just created with the wizard will be opened with the plugin editor (see Figure 3).

Step 2: Declare your View Extension
Now that a plugin has been created the next step is to declare the view extension. You can do this with the extensions tab of the plugin editor or by manually editing the plugin’s manifest file, plugin.xml. For clarity, I will explain how to do it manually.

If you open up plugin.xml you should find the following.

"expressionViews"   name="ExpressionViews Plug-in"   version="1.0.0"   provider-name=""   class="expressionViews.ExpressionViewsPlugin">         "expressionViews.jar">         "*"/>                  "org.eclipse.ui"/>      "org.eclipse.core.runtime"/>   

The first step is to declare a new extension against org.eclipse.ui.views, which is a plugin itself. This is done with the following bit of XML.

"org.eclipse.ui.views">

Next, I will create a category for the views in this article. This is not a required step, but it makes finding the views easier. To create a category place the following snippet of XML inside the extension tag you just created.

"Article Views" id="article.views"/>

Next, I need to declare my view. Declaring a view is just a matter of placing the following snippet of XML inside the extension tag you just created.

"expressionViews.RegExView"   category="article.views"   name="RegEx View"   id="article.views.regex"/>

In the above, the class attribute is the actual class I will use to implement the view. The name attribute is what will appear in the view’s tab, while the id attribute uniquely identifies the view. The category attribute specifies that this view is part of the category I defined earlier. The completed plugin.xml is as follows.

"expressionViews"   name="ExpressionViews Plug-in"   version="1.0.0"   provider-name=""   class="expressionViews.ExpressionViewsPlugin">         "expressionViews.jar">         "*"/>                  "org.eclipse.ui"/>      "org.eclipse.core.runtime"/>      "org.eclipse.ui.views">      "Article Views"            id="article.views"/>      "expressionViews.RegExView"            category="article.views"            name="RegEx View"            id="article.views.regex"/>   

Step 3: Implementing the View
To implement a view we need a class that extends ViewPart. Based on what I defined earlier, the name of the class should be RegExView in the expressionViews package. Additionally, because ViewPart is abstract and implements IViewPart, RegExView must implement the createPartControl() and setFocus(). The initial implementation of RegExView is as follows.

package expressionViews;import org.eclipse.swt.widgets.Composite;import org.eclipse.ui.part.ViewPart;public class RegExView extends ViewPart {		public void createPartControl(Composite parent) {			}		public void setFocus() {		//ignored	}}

The first method, createPartControl(), is called when Eclipse first creates the view. This is the method where the UI for the view will be implemented. The second method, setFocus(), is called when the view receives focus. Due to the nature of the views defined in this article there is no need to perform any operations when focus is set.

For the RegEx view, the UI will contain several widgets. First, you need a text box for the expression itself. Second, you need a text box containing the text to evaluate the expression against as well as a label that displays results to users. Finally, you need labels for both text boxes and a button to execute the RegEx.

Building UIs using SWT is a large subject that I won’t cover in this article. For an introduction to this subject, see “SWT Creates Fast, Native-looking GUIs for Your Java Apps,” by Raghu Donepudi or “Understanding Layouts in SWT” by Carolyn MacLeod.

The first step for building the UI is to create a layout that will organize the widgets. I have decided to use GridLayout in this case. As such, the first two lines of createPartControl() are as follows.

GridLayout gridLayout = new GridLayout();parent.setLayout(gridLayout); 

The method createPartControl() accepts as a parameter as a reference to its parent composite. As such, we want to apply our layout to the parent composite since it will contain our widgets. The next step is to create the expression text box:

GridData expGridData = new GridData();expGridData.horizontalAlignment = GridData.FILL;expGridData.grabExcessHorizontalSpace = true;Label expLabel = new Label(parent, SWT.NONE);expLabel.setText("Expression");exp = new Text(parent, SWT.BORDER);exp.setLayoutData(expGridData); 

The code for the expression text box, above, does three different things. First, a GridData object is created, which specifies the proper layout of a widget in a cell of the GridLayout. Next, a label widget is created and given a string of “Expression” to identify the expression text box. Finally, a text widget is created and the GridData applied to it. You’ll notice that the text is not declared here. I have chosen to make the expression text widget a private field of the class, so it can be easily referenced by other methods.
With that done, the next step is to create the text widget for the search text. This step is largely the same as the last step, but I have included it below:

GridData searchGridData = new GridData();searchGridData.horizontalAlignment = GridData.FILL;searchGridData.verticalAlignment = GridData.FILL;searchGridData.grabExcessHorizontalSpace = true;searchGridData.grabExcessVerticalSpace = true;Label searchLabel = new Label(parent, SWT.NONE);searchLabel.setText("Search Text");search = new Text(parent, SWT.BORDER);search.setLayoutData(searchGridData); 

Just like the expression text widget, the search text widget is also a private field of the class, thus ensuring it can be accessed by other methods. Next, the result label and evaluate button are created.

result = new Label(parent, SWT.NONE);Button eval = new Button(parent, SWT.BORDER);eval.setText("Evaluate");eval.addSelectionListener(this); 

You’ll notice that result is also not declared. This is because I made it a private field, for the same reason as with the expression and search widgets. Notably, the evaluate button has a selection listener added, which is done so that the class receives a SelectionEvent when the button is clicked. This also means that the class must implement the SelectionListener interface. Finally, the parent composite should be packed, so that all the widgets are drawn correctly, which is done by calling the pack() method on parent.

The final step is to add a widgetSelected method to the class, which is required by the SelectionListener interface. This method is included below.

public void widgetSelected(SelectionEvent e) {	String text = search.getText();	Pattern pattern = Pattern.compile(exp.getText());	Matcher matcher = pattern.matcher(text);		matcher.find();	result.setText(text.substring(matcher.start(), matcher.end()));	result.pack();}

The above method is called whenever the Evaluate button is pressed. To make it work, I compile the RegEx into a Pattern and then get a Matcher for it against the search text. Next, I get the first match and put the result into the result label and pack it so that it is redrawn.

Figure 4. RegEx View: Here’s a look at the newly created RegEx view.

With that the first view is complete and the plugin can be tested. There are several ways to test Eclipse plugins, but I recommend using the Runtime-Workbench. It allows you to run another instance of Eclipse inside of Eclipse complete with step debugging if you so desire (for more information see “Running a Plugin“).

Once you have Eclipse running with the newly created plugin you should be able to use the Window menu to open the view you’ve just created (see Figure 4).

Once the view is open you are free to create a RegEx and evaluate it against the search text. The results may not be impressive, but they are certainly useful. From here there are lots of possible enhancements one could make to the view. For example, it would be nice to have the search text matches be highlighted in the search text. It would also be nice if multiple matches found were displayed instead of just the first match.

The XPath View
With the RegEx view complete, I can now move on to the XPath view. The UI for the XPath view is exactly the same as the RegEx view, so really the only thing that needs to change is the widgetSelected() method. As such, the proper thing to do would be to create an abstract base class that both the RegEx and XPath views would extend. Whether you decide to do that or simply copy the RegEx view, the new widgetSelected() method for the XPath view is below:

public void widgetSelected(SelectionEvent e) {	try {	DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();	DocumentBuilder parser = fact.newDocumentBuilder();	Document doc = parser.parse(new StringBufferInputStream(search.getText()));	doc.normalize();		XObject obj = XPathAPI.eval(doc, exp.getText());	if(obj.getType() == XObject.CLASS_NODESET) {		NodeList nodes = obj.nodelist();		StringBuffer buf = new StringBuffer();		for(int itr = 0; itr < nodes.getLength(); itr++)			buf.append("[").append(nodes.item(itr).toString()).append("]");				result.setText("Result:  " + buf.toString());	}	else		result.setText("Result:  " + obj.str());	result.pack();	}	catch(Exception ex) {		ex.printStackTrace();	}}

As you can see from the code, evaluating an XPath is a little more complicated. First, the XML contained in the search text widget must be parsed into a Document instance. With that done, the eval() method of XPathAPI is called with the parsed Document and the expression contained in the expression text widget. The result of this can vary depending on the XPath. The code above looks for two types of results. If the result is a NodeSet then I loop over the nodes and place them inside brackets. Otherwise, the assumption is that the result can be handled as a string.

Before being able to test the newly created XPath view, you will need to declare it in the plugin manifest file. Below is the XML snippet I used:

"expressionViews.XPathView"     category="article.views"     name="XPath View"     id="article.views.xpath"/>

I hope this article made evident just how easy it is to extend Eclipse with new views. While useful, these views are far from complete. Simple, expected functionality such as copy and paste aren't implemented nor is the ability to evaluate expressions against open files. The Eclipse API is rich and has solutions for these problems and a host of others.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles:

About

DevX is the leading provider of technical information, tools, and services for professionals developing corporate applications. 

Subscribe

Get exclusive access to the best technical information, tools, and services sent straight to your inbox.  

©2023 Copyright DevX - All Rights Reserved. Registration or use of this site constitutes acceptance of our Terms of Service and Privacy Policy.