Level: Intermediate Nathan A. Good (mail@nathanagood.com), Author and Software Engineer, Consultant
26 Jun 2007 Find out how to build a plug-in for Eclipse and Rational Application Developer V7. You can use this plug-in to define snippets that let you add
code that follows enterprise standards. Similar to the Snippets view that comes with the Web Tools Project, this plug-in allows you to
drag and drop pieces of code into the editor. We follow object-oriented best practices so the snippets can be loaded from any source,
such as a database (like Apache Derby), filesystem, or Web service.
This isn't just about building a new plug-in for Eclipse, although it covers that in fine detail. In the process of building the plug-in for
Eclipse, you learn how to use features of the Eclipse integrated development environment (IDE) to quickly step through the article. I'll
also show you how to extend parts of the Eclipse IDE to add rich functionality to your plug-in. After completion, you should be able to use the
Eclipse IDE plug-in development wizard to build a new plug-in. And you learn how to extend classes and implement interfaces you can use to
add preference pages, property pages, and drag-and-drop support to your plug-in.
You should have a good understanding of object-oriented (OO) concepts like implementing interfaces and
extending classes. You should also be fairly familiar with the Eclipse IDE, but you don't have to know how to create a plug-in for Eclipse yet. You
need Eclipse V3.2 or later. You need to run a nested instance of Eclipse to test your plug-in, so your computer should have enough RAM to run
two instances of Eclipse simultaneously.
Starting your plug-in project
The Eclipse IDE is built on an extensible framework that makes it possible to write your own plug-ins for the IDE. But it doesn't end there:
Eclipse also comes with templates you can use to quickly get a good start in your Eclipse plug-in project.
To make the article meaningful, I looked for a scenario that encompasses many activities to showcase how you can use the Eclipse
framework to build a plug-in. The scenario I chose is an enterprise code snippet plug-in. This plug-in provides you with the ability to use
predefined, categorized code snippets that can be retrieved for a source outside Eclipse and inserted into your editor.
The code snippet plug-in for "Example.com" has the following features:
- A tree view you can use to find snippets by category
- A preferences page you can use to configure the location from which snippets are loaded
- A context-sensitive menu you can use to insert the snippet into an open editor
- Snippets with template variables in the form
${variable} and a wizard that gathers your values for these variables
Creating the project
To get started, create a new project in which to develop your plug-in. Fortunately, a wizard is available to guide you. Follow these steps:
- Choose File > New > Project.
- Under Plug-in Development, choose Plug-in Project and click Next.
- Next to Project name, type
SnippetsPlugin and click Next.
- On the Plug-in Content screen, leave all the defaults as is and click Next.
- Under the Templates view, select Custom plug-in wizard and click Next.
- In the list of available templates, click the Deselect All button, then select the following templates:
-
"Hello world" Action Set
-
Popup Menu
-
Preference Page
-
View
Details about how each of these contributes to your project are listed in "Understanding the templates."
- Under Sample Action Set:
- Change Action Class Name to
SnippetAction.
- Click Next to continue.
- Under Sample Popup Menu:
- Change Name Filter to
*.*.
- Change Submenu Name to
Snippet Submenu.
- Change Action Class to
SnippetPopupAction.
- Click Next to continue.
- Under Sample Preference Page:
- Change Page Class Name to
SnippetPreferencePage.
- Change Page Name to
Snippet Preferences.
- Click Next to continue.
- In the Main View Settings:
- Change View Class Name to
SnippetsView.
- Change View Name to
Example.com Snippets View.
- Change View Category Name to
Example.com Enterprise.
- Select Tree viewer for the viewer type.
- Click Next to continue.
- On the View Features screen, leave all the options checked and click Finish to create your plug-in project.
Once the Eclipse IDE is done processing, you should have a new plug-in project with a number of files in it. The templates you selected and
configured in the previous steps created several packages and Java™ source files in them. To learn more about each of these
templates, read "Understanding the templates." If you already know about the templates or don't want to spend
time going into the details, feel free to skip ahead to "Testing your plug- in."
Understanding the templates
After watching Eclipse create your new project, you may wonder what just happened. If you followed the previous steps,
your project includes a bunch of packages containing Java source files, as well as a number of other files. If you're new to plug-in
development, you may not know what they do or how they got there, which can be a bad side-effect of using wizards.
This section discusses the templates used in this article. It covers the various configuration options and how the pieces fit together to
make the plug-in. After reading the next section, you'll know enough to build these parts by hand if you elect to do so.
In the Eclipse IDE, an action set is a group of actions for menu items or buttons that should be grouped together logically. A plug-in
may have an action set because it's natural that all the actions that work together with a plug-in should be grouped together.
The "Hello world" Action Set template adds the SnippetAction.java file in the snippetssample.action package.
The name of the class is set by the Action Class Name on the Sample Action Set configuration, shown below.
Figure 1. Configuring the action set
When you run the plug-in in Eclipse, the "Hello, world" Action Set shows up as a menu labeled Sample Menu in the menu bar.
The text used for the menu label is defined in the plugin.xml file and is shown below.
Listing 1. Action sets in plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
point="org.eclipse.ui.actionSets">
<actionSet
label="Sample Action Set"
visible="true"
id="SnippetsPlugin.actionSet">
<menu
label="Sample &Menu"
id="sampleMenu">
<separator
name="sampleGroup">
</separator>
</menu>
<action
label="&Sample Action"
icon="icons/sample.gif"
class="snippetsplugin.actions.SnippetAction"
tooltip="Hello, Eclipse world"
menubarPath="sampleMenu/sampleGroup"
toolbarPath="sampleGroup"
id="snippetsplugin.actions.SnippetAction">
</action>
</actionSet>
</extension>
...
</plugin>
|
If you click the menu in Eclipse, the code in the run method of the SnippetAction
class executes. The template code shown below displays a message box.
Listing 2. SnippetAction run() method
public void run(IAction action) {
MessageDialog.openInformation(
window.getShell(),
"SnippetsPlugin Plug-in",
"Hello, Eclipse world");
}
|
Popup Menu
The Popup Menu template provides a menu item that appears when you right-click a file — sometimes known as a
context-sensitive menu. When you right-click a file in the Package Explorer, you see a menu labeled Snippet Submenu,
containing a single menu item called New Action. The Sample Popup Menu configuration is shown below.
Figure 2. Configuring the pop-up menu
The Target Object's Class points by default to org.eclipse.core.resources.IFile, which tells Eclipse to
apply the pop-up menu only when the user right-clicks a file. Other resources you'll likely use are IProject and
IFolder. Using IProject shows the menu only when the user right-clicks a project,
and using IFolder shows the menu only when the user right-clicks a folder.
The Name Filter lets you show the pop-up menu only when certain files are selected. For instance, if you specify
*.html for the filter, the item appears only when the user right-clicks a file that has a name ending in .html.
This value is defined in plugin.xml once the wizard has created the project.
The Submenu Name is the name of the menu item in the pop-up menu that displays the submenu. After creating the project, you can
change the value in the plugin.xml file.
The Action Label is the name of the menu item that appears in the submenu. This value is also written to the plugin.xml file, where you
may change it later.
The Java Package Name is the name of the package that contains the new class. The class name is defined by Action Class.
These can be modified later like the other values, but not as easily. Renaming the package and class requires you to use Eclipse's refactoring
features to make sure the references in plugin.xml are updated accordingly.
Listing 3 shows the parts of the plugin.xml file used by the pop-up menu extension.
Listing 3. Pop-up menu extension
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
...
<extension
point="org.eclipse.ui.popupMenus">
<objectContribution
objectClass="org.eclipse.core.resources.IFile"
nameFilter="*.*"
id="SnippetsPlugin.contribution1">
<menu
label="Snippet Submenu"
path="additions"
id="SnippetsPlugin.menu1">
<separator
name="group1">
</separator>
</menu>
<action
label="New Action"
class="snippetsplugin.popup.actions.SnippetPopupAction"
menubarPath="SnippetsPlugin.menu1/group1"
enablesFor="1"
id="SnippetsPlugin.newAction">
</action>
</objectContribution>
</extension>
...
</plugin>
|
When you click the pop-up menu item, Eclipse executes the code in the run() method (shown in
Listing 4) in the SnippetPopupAction class. By default, the code displays a message box.
Listing 4. SnippetPopupAction run() method
public void run(IAction action) {
Shell shell = new Shell();
MessageDialog.openInformation(
shell,
"SnippetsPlugin Plug-in",
"New Action was executed.");
} |
Preference Page
The Preference Page template includes a page you can use to set the preferences for your plug-in. This page is visible in Eclipse from the
Window > Preferences menu item. The wizard page to set up the preference page template is shown below.
Figure 3. Configuring the preference page
The Java Package Name is the name of the package that contains the page used to draw the preference page, as well as any classes
that support it. The Page Class Name is the name of the page class, and Page Name is the name that appears in the list of
preferences. The name of the preference page, which you changed to Snippet Preferences, can be modified in the plugin.xml file.
In addition to the SnippetPreferencePage class, the template generates a couple other support classes. They're
in the same package. One of them, PreferenceConstants, contains constants used to identify the preferences.
The other, PreferenceInitializer, initializes the preferences to default values.
The great thing about using the Preference Page template is that you don't have to write code to save and retrieve preferences
— that's taken care of automatically by the controls on the preference page.
View
The View template creates a sample view that can be seen in the Eclipse IDE when you test your plug-in (see
"Testing your plug-in" to see how to do that).
The Main View Settings screen, shown below, gathers the configuration for the view extension.
Figure 4. Configuring the view
The Java Package Name is like all the others: It gathers the name of the package into which your new classes are placed.
The View Class Name is the name of the Java class used to draw the view.
The View Name is the name of the view as it's identified in the Window > Show View menu.
The View Category ID is a way to identify the category, and the View Category Name is the name of the category. The category
is the group under which the view is organized when you choose to show the view using Window > Show View.
The viewer type changes how the view class (SnippetView) is generated. If you select Table viewer, the
view shows only a list of items. This list of items is generated by the getElements() method.
If the viewer type is Tree viewer, the view is generated differently. The getElements() method is altered to
return a set of TreeObject objects, and the inner class ViewContentProvider is modified
to also implement the ITreeContentProvider interface.
View classes
The SnippetView class generated from the View template is the most complex of any of the classes built in
this project. If you view the class in the Outline view in Eclipse, you'll see that it has the inner classes listed here:
-
TreeObject
- A class that represents the leaf node of a tree of items. It doesn't contain other items.
-
TreeParent
- A class that represents an item in a tree that can contain other items.
-
ViewContentProvider
- The class that implements
IStructuredProvider and ITreeContentProvider, and
gets the content to display in the view. Later in this article, you modify this class the most to provide the code snippet details to the view.
-
ViewLabelProvider
- A class that provides the icon images for the tree items.
-
NameSorter
- A class that can be modified to implement custom sorting features for the items in the view. In this article, you leave this alone. For more
information about it, view Eclipse platform API documentation for the
ViewerSorter class (see
Resources).
Testing your plug-in
So far, you have a number of Java classes in your project that were generated from the templates you chose. Before proceeding, you should
test the plug-in to see what the templates do. To do so, you need to run an instance of Eclipse. An easy way to run this nested instance of Eclipse
is to right-click the plug-in project in the Package Explorer view and select Run As > Eclipse Application from the pop-up menu.
Once the nested instance of Eclipse has started, you can see what the different pieces of the plug-in look like when they're running. To see
the action-set classes, look at the menu bar in Eclipse. The Sample Menu menu appears in the menu bar. Click Sample Menu
> Sample Item, and Eclipse displays a message box with the title SnippetSample Plug-in that says, "Hello, Eclipse world."
You can quit the nested instance of Eclipse and change the value of the string shown in bold in Listing 5. It's located in the
SnippetAction class.
Listing 5. Message displayed by SnippetAction
public void run(IAction action) {
MessageDialog.openInformation(
window.getShell(),
"SnippetsPlugin Plug-in",
"Hello, Eclipse world");
}
|
Run the nested instance of Eclipse again, select the menu item, and see it changed to the new value. If you don't like the name of the
menu or the menu item, you can change either one in the plugin.xml file. They're shown in bold below.
Listing 6. Menu item names in plugin.xml
<menu
label="Sample &Menu"
id="sampleMenu">
<separator
name="sampleGroup">
</separator>
</menu>
<action
label="&Sample Action"
icon="icons/sample.gif"
class="snippetsplugin.actions.SnippetAction"
tooltip="Hello, Eclipse world"
menubarPath="sampleMenu/sampleGroup"
toolbarPath="sampleGroup"
id="snippetsplugin.actions.SnippetAction">
</action>
|
The pop-up menu in Eclipse
To see the pop-up menu in Eclipse, start the nested instance. Once it has started, locate a file in the Package Explorer view. If no file exists,
create a project containing a sample file. When you right-click the file, you see a Snippet Submenu menu with a single
New Action submenu. Click the menu item, and Eclipse displays a message box that says "New Action was executed."
Preference page in Eclipse
To see the preference page in Eclipse, choose Window > Preferences from the nested instance of Eclipse. You should see a
preference category called Snippet Preferences. Select the category from the list, and a preference page opens with a bunch of controls
on it. These controls were included automatically with the template.
Later, you change these preferences to be more meaningful to the plug-in. For now, you can modify any of the values, apply them with the
Apply or OK button, then go back and see that the modification you made was saved and reloaded. This was all done without your
having to add any custom code to handle it.
The code to draw the preferences page is found in the SnippetPreferencePage class in the method shown below.
Listing 7. SnippetPreferencePage createFieldEditors
()
public void createFieldEditors() {
addField(new DirectoryFieldEditor(PreferenceConstants.P_PATH,
"&Directory preference:", getFieldEditorParent()));
addField(
new BooleanFieldEditor(
PreferenceConstants.P_BOOLEAN,
"&An example of a boolean preference",
getFieldEditorParent()));
addField(new RadioGroupFieldEditor(
PreferenceConstants.P_CHOICE,
"An example of a multiple-choice preference",
1,
new String[][] { { "&Choice 1", "choice1" }, {
"C&hoice 2", "choice2" }
}, getFieldEditorParent()));
addField(
new StringFieldEditor(PreferenceConstants.P_STRING,
"A &text preference:", getFieldEditorParent()));
}
|
The view in Eclipse
To see the plug-in's view in the nested instance of Eclipse, choose Window > Show View > Other. The view is organized under
the Example.com Snippets category, which was specified in the View configuration (see Figure 4) and is located in plugin.xml. Open the view by selecting the Snippet View and clicking OK.
By default, the view contains a few items. You should see a tree of items in the view like that shown in Figure 5.
Figure 5. A populated view
If you double-click any of the items in the view, Eclipse displays a message box that says, "Double-click detected on XXX", where XXX
is the tree item's text. Two additional actions have been added to the view; they appear in the view's menu bar and when you right-click anywhere
in the view. These actions are added dynamically in the makeActions() method shown below.
Listing 8. The makeActions() method
private void makeActions() {
action1 = new Action() {
public void run() {
showMessage("Action 1 executed");
}
};
action1.setText("Action 1");
action1.setToolTipText("Action 1 tooltip");
action1.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().
getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));
action2 = new Action() {
public void run() {
showMessage("Action 2 executed");
}
};
action2.setText("Action 2");
action2.setToolTipText("Action 2 tooltip");
action2.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().
getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));
doubleClickAction = new Action() {
public void run() {
ISelection selection = viewer.getSelection();
Object obj = ((IStructuredSelection)selection).getFirstElement();
showMessage("Double-click detected on "+obj.toString());
}
};
}
|
Building the SnippetProvider
To build a framework of classes to get the code snippets, you'll first write an interface, then build the implementation classes that do the work.
The interface will be used by the SnippetView to load the snippets for display. But the
SnippetView class shouldn't have to know the details about how the snippets — where they're
loaded or how they were loaded — which is why it's important to use an interface.
Start by creating the SnippetProvider interface. Choose File > New > Interface, and put the interface
in the com.example.plugins.snippets.providers package, which will also contain the implementation and
factory classes.
The contents of SnippetProvider.java are shown below.
Listing 9. The SnippetProvider interface
public interface SnippetProvider {
public String[] getLanguages() throws SnippetProviderException;
public String[] getCategories(String language) throws SnippetProviderException;
public SnippetInfo[] getSnippetInfo(String language, String category)
throws SnippetProviderException;
public Snippet getSnippet(SnippetInfo info);
public void configure(Properties props) throws SnippetProviderConfigurationException;
} |
The purpose of this plug-in is to pull code snippets from a repository of some kind, so you don't need methods to save or update the
snippets. There are only methods used to get languages (as in Java or XML), categories, information about the snippets, and the snippets
themselves. These methods, along with their descriptions, are shown below.
Table 1. Methods on the SnippetProvider interface
| Method | Description |
|---|
configure(Properties props)
| Configures the adapter. |
getCategories(String language)
| Each Language (Java, XML) has a set of categories into which the snippets may be
organized. |
getLanguages()
| Gets a list of the languages, which are the high-level means of categorizing snippets. |
getSnippet(SnippetInfo info)
| Returns a specific snippet. |
getSnippetInfo(String language, String category)
| Gets an array of SnippetInfo objects that can be shown in the view. |
SnippetFileProvider implementation class
Once the interface is finished, it's easy to start building an implementation class that uses the interface. In this section, you add an
implementation class used to grab code snippets from a directory in the filesystem.
 |
But that's not all ...
You may notice two classes that we won't discuss much: SnippetInfo and Snippet.
These classes are available in the download that accompanies this article. There are also a few exception
classes (like SnippetProviderException), a factory class that is used to load the
SnippetProvider implementation (SnippetProviderFactory), and a few others.
These support classes are useful, but it's not meaningful to focus on building them here.
|
|
Rather than adding implementing the interface methods by hand into a new class, you use Eclipse to build the start of the class. To get the
IDE to do as much work as possible, right-click the com.example.plugins.snippets.providers package, and select
New > Class. Enter SnippetFileProvider in Name and click the Add button next to the
list of interfaces. In Choose interfaces, type SnippetFileProvider and click OK.
The Eclipse IDE generates a new class file that properly implements the SnippetProvider interface. All the
methods are generated, along with the return statements, so the project cleanly compiles.
The new class is used to load code snippets from the filesystem using folders for the languages and categories. It loads the code snippet
information found an XML file called snippets.info. The file contains an XML representation of an array of
SnippetInfo items serialized to XML using the XMLEncoder class.
The configure() method, shown below, provides a way to configure the adapter given a
Properties object. For the SnippetFileProvider, the only property that is set is
the base Uniform Resource Identifier (URI), which is the base path of the directory containing the code snippet structure.
Listing 10. SnippetFileProvider configure()
public void configure(Properties props)
throws SnippetProviderConfigurationException {
this.properties = props;
String uriPath = "";
if (baseUri == null) {
try {
uriPath = properties
.getProperty("snippetFileProvider.base.directory");
if (uriPath == null || uriPath.length() == 0) {
throw new SnippetProviderConfigurationException(
"Please supply a value for property " +
"'snippetFileProvider.base.directory'");
}
baseUri = new URI(uriPath);
} catch (URISyntaxException urie) {
throw new SnippetProviderConfigurationException("URI '"
+ uriPath + "' incorrectly formatted.", urie);
}
}
} |
The getLanguages() method pulls the names of the directories directly under the base directory. Those are
used for the languages — such as Java, XML, and HTML — that are used as the highest-level categorization
of the code snippets. The method as implemented in the SnippetFileProvider class is shown below.
Listing 11. SnippetFileProvider getLanguages()
public String[] getLanguages() throws SnippetProviderException {
/*
* The languages will be the high-level directories right underneath the
* base directory.
*/
if (languages == null) {
languages = getFormattedNamesFromLocation(getBaseUri());
}
return languages;
} |
The method calls a private static method called getFormattedNamesFromLocation, passing it the base URI.
The static method returns an array of Strings that contains the properly formatted names of the items found in the
directory. Each language category contains another set of folders used to categorize the snippets into subcategories. One example for the
Java language might be a category for exception-handling or one for logging. The method for loading the categories for a language is
shown below.
Listing 12. SnippetFileProvider getCategories()
public String[] getCategories(String language)
throws SnippetProviderException {
try {
return getFormattedNamesFromLocation(new URI(getBaseUri().getPath()
+ "/" + language));
} catch (URISyntaxException e) {
throw new SnippetProviderException(
"Error while loading the categories", e);
}
} |
The getSnippetInfo() method, given a language and a category, loads an array of
SnippetInfo objects from the snippets.info file found in the category folder. The XML file contains information about
the code snippets found in the category, such as the name, description, and variables. The method is shown below.
Listing 13. SnippetFileProvider getSnippetInfo() and getSnippet() methods
public SnippetInfo[] getSnippetInfo(String language, String category)
throws SnippetProviderException {
/* Dehydrate the snippet info from the filesystem */
XMLDecoder decoder = null;
SnippetInfo[] snippetInfo = null;
try {
decoder = new XMLDecoder(new BufferedInputStream(
new FileInputStream(buildSnippetInfoPath(getBaseUri(),
language, category))));
snippetInfo = (SnippetInfo[]) decoder.readObject();
} catch (FileNotFoundException e) {
throw new SnippetProviderException(
"Could not load the snippet info index.", e);
} finally {
if (decoder != null) {
decoder.close();
}
decoder = null;
}
return snippetInfo;
}
public Snippet getSnippet(SnippetInfo info) {
Snippet snippet = null;
String snippetPath = buildSnippetPath(getBaseUri(), info);
/* Load the snippet from the file */
BufferedInputStream stream = null;
BufferedReader reader = null;
try {
stream = new BufferedInputStream(new FileInputStream(snippetPath));
reader = new BufferedReader(new InputStreamReader(
stream));
String line;
StringBuffer sb = new StringBuffer();
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
snippet = new Snippet();
snippet.setContent(sb.toString());
sb = null;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException ioe) {
// TODO Auto-generated catch block
ioe.printStackTrace();
}
finally
{
try {
if (reader != null) {
reader.close();
}
if (stream != null) {
stream.close();
}
} catch (IOException ioe) {
}
}
return snippet;
} |
Finally, the getSnippet() method (also shown above) loads the code snippet from
the filesystem and returns it in a Snippet object. The rest of the methods in the class are private methods used to
support the public methods. You can inspect them more closely in the source code included as a download with this article.
Populating your view
Armed with the framework for getting code snippets from a directory on the filesystem, you should be ready to add appropriate code to the
SnippetView class to show the snippet information in the tree in the plug-in's view. To show the snippets in the
view, you modify the initialize() method of the ViewContentProvider inner class in
SnippetView.
The initialize() method builds the tree of items shown in the view. When the class is initially created from the
template, it includes a bunch of dummy data, so you have something to look at when you run the plug-in with Eclipse. If you've walked through
the steps so far, you should be ready to change this method to use the SnippetProvider to load snippet information.
The new method is shown below.
Listing 14. The new SnippetsView initialize()
private void initialize() {
invisibleRoot = new TreeParent("");
/*
* Get the high-level elements from the provider, which are the
* languages.
*/
String[] topLevelNodes;
try {
Properties properties = new Properties();
InputStream is = null;
is = SnippetProviderFactory.class
.getResourceAsStream("/SnippetProvider.properties");
properties.load(is);
if (is != null) {
try {
is.close();
} catch (IOException innerE) {
throw new SnippetProviderException(
"Could not close resource stream.", innerE);
}
}
snippetProvider = SnippetProviderFactory.createInstance();
snippetProvider.configure(properties);
topLevelNodes = snippetProvider.getLanguages();
for (int i = 0; i < topLevelNodes.length; i++) {
TreeParent parent = new TreeParent(topLevelNodes[i]);
/* Get the categories for each one of the parents */
String[] categories = snippetProvider
.getCategories(topLevelNodes[i]);
for (int j = 0; j < categories.length; j++) {
TreeParent categoryParent = new TreeParent(
categories[j]);
/* Now get the snippet names for the categories */
SnippetInfo[] info = snippetProvider.getSnippetInfo(
topLevelNodes[i], categories[j]);
for (int k = 0; k < info.length; k++) {
TreeObject leaf = new TreeObject(info[k]);
categoryParent.addChild(leaf);
}
parent.addChild(categoryParent);
}
invisibleRoot.addChild(parent);
}
} catch (SnippetProviderConfigurationException spce) {
topLevelNodes = new String[] { "Configuration error: "
+ spce.getLocalizedMessage() };
} catch (SnippetProviderException spe) {
topLevelNodes = new String[] { "Error while loading snippets" };
} catch (IOException ioe) {
topLevelNodes = new String[] { "Error loading configuration properties:"
+ ioe.getLocalizedMessage() };
}
} |
The new code loads a class dynamically using the SnippetProviderFactory and immediately casts the result to
the SnippetProvider interface. This allows the view to be agnostic about the source of the snippets. Once the
provider's interface is obtained, the provider is configured so it knows where to get the code snippet structure. Then, a top-level node is added
for each language.
 |
Chatty?
You could easily look at the implementation of the SnippetProvider and criticize it for being "chatty" because it
makes several calls by iterating through each language or category to get the items under it. A less-chatty implementation would load the
entire structure at once. That approach might be better for a Web service implementation. The implementation class is free to use whatever
method is best for performance and scalability.
|
|
The code walks, then iterates through the languages, retrieving the categories for each language and adding them to the parent node
in the tree. For each category, the view then calls the getSnippetInfo() method to get the information about the
code snippets contained in the category. It iterates through the array and builds new TreeObjects for each
SnippetInfo.
It's important to note a couple of things about the new code. First, the SnippetProviderFactory loads a default implementation class. This must change before the plug-in is usable to get code
snippets from different places. Second, the adapter is configured from properties that are loaded from a file. That must also change because
when the plug-in is distributed, the user needs to be able to change the properties. Both of these are loaded from the preferences.
The process of loading these values from the preferences is discussed later in "Adding user preferences."
Changes to TreeObject
To be able to get all the information you need for the selected snippet in the tree view, you need to associate more than just the name with
the TreeObject. You can resolve that issue by making minor tweaks to the
TreeObject inner class. You need to replace the private String name field with a
private field that holds a SnippetInfo object. You also need to make changes to the constructor and
getName() methods.
The modified TreeObject class is shown below, with the changes in bold.
Listing 15. The modified TreeObject
class TreeObject implements IAdaptable {
private SnippetInfo info;
private TreeParent parent;
public TreeObject(SnippetInfo info) {
this.info = info;
}
public String getName() {
return info.getName();
}
public void setParent(TreeParent parent) {
this.parent = parent;
}
public TreeParent getParent() {
return parent;
}
public String toString() {
return getName();
}
public SnippetInfo getInfo() {
return info;
}
public Object getAdapter(Class key) {
return null;
}
} |
Because the TreeParent object extends the TreeObject, you need to make a couple
of changes to it, as well. The changes are shown in bold below.
Listing 16. The modified TreeParent
class TreeParent extends TreeObject {
private ArrayList children;
private String name;
public TreeParent(String name) {
super(null);
this.name = name;
children = new ArrayList();
}
@SuppressWarnings("unchecked")
public void addChild(TreeObject child) {
children.add(child);
child.setParent(this);
}
public void removeChild(TreeObject child) {
children.remove(child);
child.setParent(null);
}
@SuppressWarnings("unchecked")
public TreeObject[] getChildren() {
return (TreeObject[]) children.toArray(new TreeObject[children
.size()]);
}
public boolean hasChildren() {
return children.size() > 0;
}
@Override
public String getName() {
// TODO Auto-generated method stub
return this.name;
}
@Override
public String toString() {
return this.getName();
}
} |
The changes consist of overriding the getName() and toString() methods and
modifying the constructor to not pass a null to the superconstructor and to assign the name to a new private String
field.
Prepare the directory structure
Before you can run the new snippet provider implementation in Eclipse, you need to create a repository of sample snippets. Fortunately, the
file-based repository is easy to create. You can build your own file-based structure or use the example provided in the code included here.
To create the file-based repository yourself, first lay out a one or more directories for languages. Under each one of those directories,
add one or more directories for your categories. Finally, under each category, add a file called snippets.info; in that file, add the contents of
Listing 17.
Listing 17. Sample snippets.info
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.5.0_07" class="java.beans.XMLDecoder">
<array class="com.example.plugins.snippets.SnippetInfo" length="1">
<void index="0">
<object class="com.example.plugins.snippets.SnippetInfo">
<void property="category">
<string>Exception_Handling</string>
</void>
<void property="language">
<string>Java</string>
</void>
<void property="name">
<string>Exception class</string>
</void>
<void property="variables">
<array class="java.lang.String" length="3">
<void index="0">
<string>exception.classname</string>
</void>
<void index="1">
<string>author</string>
</void>
<void index="2">
<string>package.name</string>
</void>
</array>
</void>
</object>
</void>
</array>
</java>
|
You need to update each file to match the language and category. After you add the file, add files that contain each snippet. An example of the
directory structure is shown below.
Listing 18. Snippets file-based repository structure
+- basedir
+- Java
| +- Exception_Handling
| +- snippets.info
| +- Exception_Class.snippet
+- XML
+- Ant_Build_Files
+- snippets.info
+- Simple_File.snippet
|
View your modifications
With all the supporting classes created and the file-based code snippet repository built, you can now start a nested instance of Eclipse to
see what the new snippet listing looks like. If all is well, you should see a view that looks like Figure 6.
Figure 6. View with snippets
With the code snippet information loading properly, you're ready for the next step: adding code to bring the contents of the .snippet files into
the editor.
Write an InsertSnippetAction class
Now that the SnippetFileProvider implementation loads the snippet information into the view, you're ready
to build code that uses the snippet information to get the code snippet text from the provider and inserts that into the open editor. The
snippet information is stored in an instance of SnippetInfo, which is obtained from the item selected in the
Example.com Snippets view.
Start by building the InsertSnippetAction inner class, which contains the code that gets the snippet content
and performs the insertion. It extends the Action class so it can be used easily by the rest of the plug-in
components. It's an inner class to SnippetsView because it's used privately by the view and to offer easy
access to objects in the view.
The new InsertSnippetAction class is shown below, with a message box in the
run() method. The message box is useful to make sure the new
InsertSnippetAction is built correctly and added to the appropriate places.
Listing 19. InsertSnippetAction
class InsertSnippetAction extends Action {
@Override
public void run() {
MessageDialog.openInformation(
window.getShell(),
"SnippetsSample Plug-in",
"Running Insert Action Now!");
}
} |
Changes to SnippetView
When the SnippetView is created initially from its template, it contains a couple of sample actions that are
accessible from the view's toolbar and pop-up menu. These are local actions and aren't set up in the plugin.xml file as extension points.
This is fine because the view owns these actions completely and doesn't contribute them to any other parts of the IDE.
After you build the InsertSnippetAction class, open the
SnippetsView class and remove action2 and
doubleClickAction. Keep action1 for now — you'll be able to use
Eclipse's refactoring tools to rename it insertAction and change the type from Action
to InsertAction. Because InsertAction extends Action,
you don't need to change anything else. Listing 20 shows the new SnippetsView class with the changes in bold.
Listing 20. SnippetsView
public class SnippetsView extends ViewPart implements ISelectionListener {
private TreeViewer viewer;
private DrillDownAdapter drillDownAdapter;
private InsertSnippetAction insertAction;
private SnippetProvider snippetProvider;
class InsertSnippetAction extends Action {
// Snipped...
}
class TreeObject implements IAdaptable {
// Snipped...
}
class TreeParent extends TreeObject {
// Snipped...
}
class ViewContentProvider implements IStructuredContentProvider,
ITreeContentProvider {
private TreeParent invisibleRoot;
public void inputChanged(Viewer v, Object oldInput, Object newInput) {
}
public void dispose() {
}
public Object[] getElements(Object parent) {
if (parent.equals(getViewSite())) {
if (invisibleRoot == null)
initialize();
return getChildren(invisibleRoot);
}
return getChildren(parent);
}
public Object getParent(Object child) {
if (child instanceof TreeObject) {
return ((TreeObject) child).getParent();
}
return null;
}
public Object[] getChildren(Object parent) {
if (parent instanceof TreeParent) {
return ((TreeParent) parent).getChildren();
}
return new Object[0];
}
public boolean hasChildren(Object parent) {
if (parent instanceof TreeParent)
return ((TreeParent) parent).hasChildren();
return false;
}
private void initialize() {
// Snipped... see earlier Listing.
}
}
class ViewLabelProvider extends LabelProvider {
public String getText(Object obj) {
return obj.toString();
}
public Image getImage(Object obj) {
String imageKey = ISharedImages.IMG_OBJ_ELEMENT;
if (obj instanceof TreeParent)
imageKey = ISharedImages.IMG_OBJ_FOLDER;
return PlatformUI.getWorkbench().getSharedImages().getImage(
imageKey);
}
}
class NameSorter extends ViewerSorter {
}
/**
* The constructor.
*/
public SnippetsView() {
}
/**
* This is a callback that will allow us to create the viewer and initialize
* it.
*/
public void createPartControl(Composite parent) {
viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
drillDownAdapter = new DrillDownAdapter(viewer);
viewer.setContentProvider(new ViewContentProvider());
viewer.setLabelProvider(new ViewLabelProvider());
viewer.setSorter(new NameSorter());
viewer.setInput(getViewSite());
// add myself as a global selection listener
getSite().getPage().addSelectionListener(this);
// prime the selection
selectionChanged(null, getSite().getPage().getSelection());
makeActions();
hookContextMenu();
hookDoubleClickAction();
contributeToActionBars();
}
private void hookContextMenu() {
MenuManager menuMgr = new MenuManager("#PopupMenu");
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
SnippetsView.this.fillContextMenu(manager);
}
});
Menu menu = menuMgr.createContextMenu(viewer.getControl());
viewer.getControl().setMenu(menu);
getSite().registerContextMenu(menuMgr, viewer);
}
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
fillLocalPullDown(bars.getMenuManager());
fillLocalToolBar(bars.getToolBarManager());
}
private void fillLocalPullDown(IMenuManager manager) {
manager.add(insertAction);
manager.add(new Separator());
}
private void fillContextMenu(IMenuManager manager) {
manager.add(insertAction);
manager.add(new Separator());
drillDownAdapter.addNavigationActions(manager);
// Other plug-ins can contribute there actions here
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
private void fillLocalToolBar(IToolBarManager manager) {
manager.add(insertAction);
manager.add(new Separator());
drillDownAdapter.addNavigationActions(manager);
}
private void makeActions() {
insertAction = new InsertSnippetAction();
insertAction.setText("Insert Snippet");
insertAction.setToolTipText("Inserts the selected snippet");
insertAction.setImageDescriptor(PlatformUI.getWorkbench()
.getSharedImages().getImageDescriptor(
ISharedImages.IMG_OBJS_INFO_TSK));
}
private void hookDoubleClickAction() {
viewer.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
insertAction.run();
}
});
}
/**
* Passing the focus request to the viewer's control.
*/
public void setFocus() {
viewer.getControl().setFocus();
}
public void selectionChanged(IWorkbenchPart part, ISelection selection) {
}
}
|
When you test the plug-in, you see that double-clicking a tree item in Eclipse displays a message that says, "Running insert action on XXX",
where XXX is the text of the selected item in the Example.com Snippets view. The same action is executed if you right-click and choose
Action 1 from the context-sensitive menu or click the button in the view's menu bar.
Finishing InsertAction
If all the double-click and pop-up menu items work, you can finish the InsertSnippetAction class by adding the
rest of the code. The new and improved run() method is shown below.
Listing 21. The completed InsertSnippetAction run()
class InsertSnippetAction extends Action {
@Override
public void run() {
IEditorPart target = getViewSite().getWorkbenchWindow()
.getActivePage().getActiveEditor();
if (target != null) {
ITextEditor textEditor = null;
if (target instanceof ITextEditor) {
textEditor = (ITextEditor) target;
ISelectionProvider sp = textEditor.getSelectionProvider();
ITextSelection sel = (ITextSelection) sp.getSelection();
IDocument doc = textEditor.getDocumentProvider()
.getDocument(textEditor.getEditorInput());
try {
String text = getMergedSnippetContent();
doc.replace(sel.getOffset(), sel.getLength(), text);
} catch (BadLocationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
|
The run() method calls getMergedSnippetContent(), which is shown below. The
rest of the method is code used to get an instance of the current editor, get the text in the editor that is selected and replace the selected
text with the value returned by getMergedSnippetContent().
Listing 22. getMergedSnippetContent() method
public String getMergedSnippetContent()
{
String result = "";
ISelection selection = viewer.getSelection();
Object obj = ((IStructuredSelection) selection).getFirstElement();
if (obj instanceof TreeObject) {
SnippetInfo info = ((TreeObject) obj).getInfo();
Snippet s = snippetProvider.getSnippet(info);
SnippetVariablesWizardPage page = new SnippetVariablesWizardPage(
"", info, s);
page.setTitle("Snippet Variables");
page.setDescription("Input the values to replace the template "
+ "variables found in this snippet.");
// create wizard
SnippetVariablesWizard wizard = new SnippetVariablesWizard(page);
// create wizard dialog & launch wizard dialog
WizardDialog dialog = new WizardDialog(viewer.getControl()
.getShell(), wizard);
dialog.open();
result = s.getMergedContent();
}
return result;
} |
This method uses a wizard and a wizard page to gather input from the user for the snippet variables. If you try to compile the code right now,
as shown above, you'll get errors. That's because the SnippetVariablesWizardPage and
SnippetVariablesWizard haven't been created yet. You build those in the next section.
Adding user input
Building a mechanism to gather user input for the code snippet template variables is a surprisingly simple task, although it requires quite a
few moving parts. The moving parts are in the form of Standard Widget Toolkit (SWT) elements and helpers that until now you haven't worried
about because the templates took care of the ugly details. However, when built from the ground up in the correct order, the pieces fit together
nicely. This section shows how to build a wizard page with a table on it for gathering user input for the snippet variables.
SnippetVariablesWizardPage
SnippetVariablesWizardPage is a class that extends the WizardPage class.
The WizardPage class is part of the Eclipse Platform API and offers you the base implementation of a wizard page.
See Resources for Eclipse Platform API documentation.
There are more specialized incarnations of the WizardPage, such as
WizardNewFileCreationPage, which is used to gather input for creating a new file. But in this project, you start with
a clean slate, so WizardPage does just fine. The full listing of
SnippetVariablesWizardPage is shown below. It has a couple of inner classes that make it look a little complex,
but almost all of that code supports the table that draws the properties for the user. The table is editable, which explains the amount of code
used to build it. If the table was read-only data, you could iterate quickly through the Table object and add the
rows. If you look at the SnippetsView class, you see many inner classes that implement the same interfaces as
those in this wizard page.
Listing 23. The SnippetVariablesWizardPage class
public class SnippetVariablesWizardPage extends WizardPage {
// Use the Table widget
// http://www.eclipse.org/swt/widgets/
// http://help.eclipse.org/help31/nftopic/org.eclipse.platform.doc.isv
/reference/api/org/eclipse/swt/widgets/Table.html
private Table propertyTable;
private TableViewer tableViewer;
private SnippetInfo snippetInfo;
private Snippet snippet;
private static final String[] columnNames = new String[]{"property", "value"};
private SnippetVariableValue[] variables;
class SnippetVariableValue
{
// Snipped... see Listing 24
}
class SnippetVariableContentProvider implements IStructuredContentProvider {
// Snipped... see Listing 25
}
class SnippetVariableLabelProvider extends LabelProvider implements
ITableLabelProvider {
// Snipped... see Listing 26
}
class SnippetVariableCellModifier implements ICellModifier {
// Snipped... see Listing 27
}
private SnippetVariableValue[] initializeData(SnippetInfo info)
{
SnippetVariableValue[] result = new SnippetVariableValue[info.getVariables().length];
for (int i = 0; i < info.getVariables().length; i++) {
result[i] = new SnippetVariablesWizardPage.SnippetVariableValue(
info.getVariables()[i]);
}
return result;
}
public SnippetVariablesWizardPage(String pageName,
SnippetInfo info, Snippet snippet) {
super(pageName);
this.snippetInfo = info;
this.snippet = snippet;
this.variables = initializeData(this.snippetInfo);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.IDialogPage#createControl(
* org.eclipse.swt.widgets.Composite)
*/
public void createControl(Composite parent) {
// create main composite & set layout
Composite container = new Composite(parent, SWT.NONE);
container.setLayout(new FillLayout());
container.setLayoutData(new GridData(GridData.FILL_BOTH));
// titleText = new Text(mainComposite, SWT.BORDER | SWT.SINGLE);
propertyTable = new Table(container, SWT.BORDER | SWT.SINGLE);
propertyTable.setHeaderVisible(true);
propertyTable.setLinesVisible(true);
int colWidth = 160;
TableColumn column = new TableColumn(propertyTable, SWT.LEFT, 0);
column.setText("Property Name");
column.setWidth(colWidth);
// Second column
column = new TableColumn(propertyTable, SWT.LEFT, 1);
column.setText("Value");
column.setWidth(colWidth);
// Iterate through the variables in the snippet info and add them to the
// table.
tableViewer = new TableViewer(propertyTable);
tableViewer.setUseHashlookup(true);
tableViewer.setColumnProperties(columnNames);
CellEditor[] editors = new CellEditor[columnNames.length];
TextCellEditor textEditor = new TextCellEditor(propertyTable);
((Text) textEditor.getControl()).setTextLimit(60);
editors[0] = textEditor;
textEditor = new TextCellEditor(propertyTable);
((Text) textEditor.getControl()).setTextLimit(60);
editors[1] = textEditor;
tableViewer.setCellEditors(editors);
tableViewer.setCellModifier(new SnippetVariableCellModifier());
tableViewer.setContentProvider(new SnippetVariableContentProvider());
tableViewer.setLabelProvider(new SnippetVariableLabelProvider());
tableViewer.setInput(this.variables);
// page setting
setControl(container);
setPageComplete(true);
}
public void updateSnippet()
{
/* Get the values from the table */
HashMap map = createMap(variables);
snippet.mergeContent(map);
}
@SuppressWarnings("unchecked")
private HashMap createMap(SnippetVariableValue[] variables)
{
HashMap map = new HashMap();
for (int i = 0; i < variables.length; i++)
{
map.put(variables[i].property, variables[i].value);
}
return map;
}
}
|
Inside the createControl() method, a TableViewer object is assigned and set up,
along with Table. The table by itself is capable of holding the data to be presented to the user, but the
TableViewer offers the ability to easily add editors of different types to the columns of the table. The
TableViewer class also lets you add data into the table by using the setInput() method. The only drawback
to using the method is that the viewer requires you to add a content provider (SnippetVariableContentProvider) first.
The benefit is that the viewer can use this method to populate the table without iterating through a collection and assigning the values yourself.
SnippetVariableValue
This is a simple inner class that holds the data drawn in the table and updated by the user. It has two public fields: one to hold the property
name and one to hold the value the user enters. An array of these is drawn in the table in the wizard; one
SnippetVariableValue object exists for every variable found in the snippet.
Listing 24. SnippetVariableValue class
class SnippetVariableValue
{
public String property;
public String value;
public SnippetVariableValue(String property)
{
this.property = property;
this.value = "";
}
}
|
SnippetVariableContentProvider
The content provider is an inner class that implements the IStructuredContentProvider interface. The full inner
class is shown below.
Listing 25. SnippetVariableContentProvider class
class SnippetVariableContentProvider implements IStructuredContentProvider {
public void dispose() {
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
public Object[] getElements(Object parent) {
return variables;
}
}
|
For this article, the only method that contains implementation code is getElements(), which returns the variables
initialized in the constructor of the wizard page.
SnippetVariableLabelProvider
The label provider implements the ITableLabelProvider interface and extends the
LabelProvider class. Both of these are parts of the Eclipse Platform API. The code for the label provider is shown
below.
Listing 26. SnippetVariableLabelProvider class
class SnippetVariableLabelProvider extends LabelProvider implements
ITableLabelProvider {
public Image getColumnImage(Object element, int columnIndex) {
return null;
}
public String getColumnText(Object element, int columnIndex) {
SnippetVariableValue variable = (SnippetVariableValue) element;
if (columnIndex == 0) {
return variable.property;
} else if (columnIndex == 1) {
return (variable.value != null) ? variable.value : "Type value";
} else {
return "";
}
}
}
|
There are no images in the table — just text — so the getColumnImage()
method doesn't need to return anything. The getColumnText() method, however, needs to be modified to return
the correct text for the column index. There are more elegant solutions than hardcoding the logic for grabbing the correct values, but I don't want
to take the focus away from the rest of the work.
SnippetVariableCellModifier
The SnippetVariableCellModifier implements the ICellModifier interface from the
Eclipse Platform API. It provides methods that let you determine whether a cell can be modified, as well as retrieve the values and modify them.
The class is shown below.
Listing 27. SnippetVariableCellModifier class
class SnippetVariableCellModifier implements ICellModifier {
public boolean canModify(Object element, String property) {
return (!property.equals("property"));
}
public Object getValue(Object element, String property) {
Object result;
SnippetVariableValue variable = (SnippetVariableValue) element;
if (property.equals("property")) {
result = variable.property;
} else if (property.equals("value")) {
result = variable.value;
} else {
result = "";
}
return result;
}
public void modify(Object element, String property, Object value) {
TableItem item = (TableItem) element;
SnippetVariableValue variable = (SnippetVariableValue) item
.getData();
if (property.equals("value")) {
variable.value = (value != null) ? value.toString() : "";
}
tableViewer.update(variable, null);
}
} |
 |
Not seeing your changes?
A step in the modify() method is very important: calling
update() on the table viewer at the end. If you leave this call out, you can make
changes to the table, but they aren't displayed.
|
|
You don't want the user to modify the name of the variable in the snippet template, and that turns out to be easy to implement. Add code
to the canModify() method to return false if the name of the column is
property. If you're building a table that is editable, and it's OK for the user to edit any column, you can modify this
method to always return true.
Compared to SnippetVariablesWizardPage, SnippetVariablesWizard is a
lightweight class. The full class is shown below. It extends Wizard, a class provided by the Eclipse Platform API.
Listing 28. SnippetVariablesWizard class
public class SnippetVariablesWizard extends Wizard {
public SnippetVariablesWizard(SnippetVariablesWizardPage page)
{
addPage(page);
}
@Override
public boolean performFinish() {
SnippetVariablesWizardPage page =
(SnippetVariablesWizardPage)getPages()[0];
page.updateSnippet();
return true;
}
} |
The constructor adds to the wizard using the addPage() method. Because that is taken care of in the
base Wizard class, you don't need to worry about how it's implemented. The
performFinish() method runs when the user clicks the Finish button in the wizard. It needs to return
true for the wizard to close; if it returns false, the wizard remains displayed to the user. In many cases where a long-running process is
possible, it's a good idea to initiate a process that implements the IRunnableWithProgress interface.
When you execute the wizard, it looks much like the one shown below.
Figure 7. The wizard
When the wizard exits, the snippet passed to the wizard page has been updated to include the merged result of the snippet text and the
values provided by the user. Now, when you run InsertSnippetAction, the wizard appears, containing the
variables in the snippet in the left column. After you add the values in the right column and click Finish, the merged result is
placed in the active text editor.
Responding to new events
After adding the wizard page and class, you should be able to load snippets using the provider and customize the values that are put
into the snippets. To make the plug-in more polished, you can add drag-and-drop functionality, in addition to the pop-up menu and
double-click event. The Eclipse Platform API includes classes you can use to implement drag and drop between the Example.com
Snippets view and the editor. If you select a tree object and drag it into an editor, the wizard appears, prompting you for values. Then,
the merged result is put into the editor.
The SnippetDragDropListener
To add drag-and-drop support to your new view, add another inner class. This time, implement the
TransferDragSourceListener interface. The interface has three methods; you change two of them to
implement drag and drop in this article. Those methods are shown in bold below.
Listing 29. SnippetDragDropListener class
class SnippetDragDropListener implements TransferDragSourceListener {
public Transfer getTransfer() {
return TextTransfer.getInstance();
}
public void dragFinished(DragSourceEvent event) {
// There is nothing to clean up or do.
}
public void dragSetData(DragSourceEvent event) {
ISelection selection = viewer.getSelection();
Object obj = ((IStructuredSelection) selection).getFirstElement();
if (obj instanceof TreeObject) {
event.data = getMergedSnippetContent();
}
}
public void dragStart(DragSourceEvent event) {
// Always enabled, so don't do anything on the start.
}
}
|
The getTransfer() method returns a new instance of TextTransfer. This lets
Eclipse know how to handle the transfer.
The dragSetData() method calls the same private method used by the
InsertSnippetAction class (see "Writing an InsertSnippetAction Class").
However, instead of having to do anything with inserting the code into the current editor, the method sets the
event.data value. The IDE takes care of the rest because it knows how to treat the transfer of data between the
two parts: as text.
Hooking it in
Before you can use drag and drop, you have to call a method to add drag-and-drop support to the
TreeViewer component in the Example.com Snippets view. The code to set this up is shown below.
Listing 30. Adding drag-and-drop support
public void createPartControl(Composite parent) {
viewer = new TreeViewer(parent, SWT.MULTI |
SWT.H_SCROLL | SWT.V_SCROLL);
drillDownAdapter = new DrillDownAdapter(viewer);
viewer.setContentProvider(new ViewContentProvider());
viewer.setLabelProvider(new ViewLabelProvider());
viewer.setSorter(new NameSorter());
viewer.setInput(getViewSite());
// add myself as a global selection listener
getSite().getPage().addSelectionListener(this);
// prime the selection
selectionChanged(null, getSite().getPage().getSelection());
makeActions();
hookContextMenu();
hookDoubleClickAction();
contributeToActionBars();
DelegatingDragAdapter dragAdapter = new DelegatingDragAdapter();
SnippetDragDropListener dragDropListener =
new SnippetDragDropListener();
dragAdapter.addDragSourceListener(
(TransferDragSourceListener) dragDropListener);
viewer.addDragSupport(DND.DROP_COPY | DND.DROP_MOVE,
dragAdapter.getTransfers(), dragAdapter);
} |
Adding user preferences
Now that the plug-in displays the code snippets in the view and you can insert snippets into your editor through a variety of actions,
the final step in this article is to update the preferences page to allow the user to set the base directory and the
SnippetProvider implementation class name.
Modifying SnippetsPreferencePage
Open the SnippetsPreferencePage and PreferenceConstants classes to
make your changes. You can remove all the default template fields except StringFieldEditor. After cleaning out
the fields you don't need, you can tweak the label for the remaining editor and add one that loads the snippet repository location.
The final code for the preference page createFieldEditors() method is shown below.
Listing 31. The SnippetsPreferencePage createFieldEditors() method
public void createFieldEditors() {
addField(new StringFieldEditor(PreferenceConstants.P_CLASS,
"Snippet &Implementation Class:", getFieldEditorParent()));
addField(new StringFieldEditor(PreferenceConstants.P_SNIPPET_REPOS_LOC,
"&Snippet Repository Path:", getFieldEditorParent()));
}
|
Make sure you update the constant names in the PreferenceConstants class to make them more meaningful.
Also update the initializeDefaultPreferences() method in the PreferenceInitializer
class to set the preferences to appropriate default values. For instance, set the default snippet provider implementation class name to a real,
valid class, and set the repository location to a valid repository path.
Summary
By using interfaces and classes available to you through the Eclipse Platform API, you can extend Eclipse to add your own rich plug-ins.
Building your own plug-ins is even easier if you start with the templates included with Eclipse. You can add custom actions to Eclipse by
implementing interfaces or extending classes like the Action class. With these actions, you can introduce
custom behavior hooked into pop-up menus, toolbar buttons, and double-click events. You can add new custom views to Eclipse and
fill them with your own information.
Classes extending TransferDragSourceListener can be used to add
drag-and-drop behavior between your custom views and parts of the Eclipse IDE. You can add custom wizards with one or more wizard
pages by extending the Wizard and WizardPage classes. With these wizards,
you can collect information from the user and then use that information to run your custom process. And finally, with Eclipse, you can test
your plug-ins quickly by running them in a nested instance of Eclipse. You can see what your plug-in looks like and how it behaves
almost instantly.
Download | Description | Name | Size | Download method |
|---|
| Sample code for this article | os-eclipse-snippet_ExampleComSnippetsPlugin.zip | 77KB | HTTP |
|---|
Resources Learn
Get products and technologies
Discuss
-
The Eclipse Platform newsgroups should be your first stop to discuss questions regarding Eclipse. (Selecting this will launch your default Usenet news reader application and open eclipse.platform.)
-
The Eclipse newsgroups has many resources for people interested in using and extending Eclipse.
-
Participate in developerWorks blogs and get involved in the developerWorks community.
About the author  | |  | Nathan Good lives in the Twin Cities area of Minnesota. When he isn't writing software, he enjoys building PCs and servers, reading about and working with new technologies, and trying to get all his friends to make the move to open source software. When he's not at a computer (which he admits isn't often), he spends time with his family, at his church, and at the movies. Visit his Web site. |
Rate this page |