This tutorial describes how to create a custom text editor for the Eclipse IDE, with code completion, syntax highlighting, hover and code mining support.
1. Eclipse Editors
A text editor allows you to modify data, typically text, and applies the changes to the underlying model whenever the user saves.
To provide editor functionality for a certain file extension or content type, you can:
-
Extend the generic editor
-
Implement your own editor
Extending the generic editor is the preferred choice for new file types, as this accelerates and simplifies the implementation.
To support a new content type in the generic editor, you need to:
-
add a content type
-
register a
PresentationReconcilerfor it via theorg.eclipse.ui.genericeditor.presentationReconcilersextension point
1.1. JFace text framework
In JFace text, a text document is modeled as an IDocument.
To view or edit an IDocument, you use an ITextViewer as the controller, which uses a StyledText widget for its presentation.
The IDocument interface stores text and provides support for:
-
line information
-
text manipulation
-
document change listeners
-
customizable position management
-
search
-
customizable partition management
-
document partition change listeners
A document can be partitioned into different partitions via a document partitioner. These partitions can be handled according to their type, for example, they can have different foreground colors.
1.2. Introduction to presentation reconciler
Every time the user changes the document, the presentation reconciler determines which region of the visual presentation should be invalidated and how to repair it.
Syntax highlighting of source code is implemented via a presentation reconciler.
Such a presentation reconciler can be defined via an org.eclipse.ui.genericeditor.presentationReconcilers extension.
It requires the specification of a contentType and a class that implements the IPresentationReconciler interface.
An IPresentationReconciler can apply IRule implementations for a specified content type.
An IRule defines a rule used when scanning text for document partitioning or text styling.
The partition is a semantic view onto the document:
-
each partition has a content type
-
each character of a document belongs to a partition
-
documents support multiple partitions
-
partitioning is always up-to-date
1.3. API for working with editors
You can open an editor via the currently active page.
For this, you need an IEditorInput object and the ID of the editor, which is defined via the org.eclipse.ui.editors extension point.
page.openEditor(new YourEditorInput(), ID_OF_THE_EDITOR);
To get the page, you can use:
// in a view
getViewSite().getPage();
// in a command handler
HandlerUtil.getActiveWorkbenchWindow(event).getActivePage();
// anywhere else
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
1.4. Using TextMate Grammars for syntax highlighting
TextMate grammars are JSON- or YAML-based syntax definitions that describe how to tokenize and style text in an editor, commonly used for syntax highlighting. They follow a hierarchical, pattern-matching approach and are widely supported, for example, by Visual Studio Code and, via plug-ins, by the Eclipse IDE.
TM4E provides a Java API to tokenize text documents according to TextMate grammars and an Eclipse IDE integration that uses this tokenization for syntax highlighting. It also supports VS Code language configuration files for bracket matching, auto-closing and on-enter rules.
2. Adding a hyperlink detector to an editor
2.1. Hyperlinks in a source editor
If you hold down the Ctrl and click on an element in the Java editor you can navigate to it.
This functionality is provided via extensions for the org.eclipse.ui.workbench.texteditor.hyperlinkDetectors extension point.
The specified name is visible in the preferences under .
The targetId points to the type of editor you want to support.
If you want to use in all text editors use org.eclipse.ui.DefaultTextEditor.
To target the generic editor use the org.eclipse.ui.genericeditor.GenericEditor target id.
An IHyperlinkDetector is supposed to return an array of IHyperlink objects.
The IHyperlink implementation performs the hyperlink action.
2.2. Adding colors and fonts preferences
Eclipse provides a page for the customizations of colors and fonts by the user under .
To define an entry for this page, you need to define an extension for the org.eclipse.ui.themes extension point.
For example, you can provide a category, font and color with the following entry in the plugin.xml file or your plug-in.
<extension point="org.eclipse.ui.themes">
<themeElementCategory
id="com.vogella.eclipse.preferences.mythemeElementCategory"
label="vogella category">
<description>
An example theme category
</description>
</themeElementCategory>
<colorDefinition
categoryId="com.vogella.eclipse.preferences.mythemeElementCategory"
id="com.vogella.eclipse.preferences.myFirstColorDefinition"
label="vogella color"
value="COLOR_DARK_BLUE">
<description>
Your description for the color
</description>
</colorDefinition>
<fontDefinition
categoryId="com.vogella.eclipse.preferences.mythemeElementCategory"
id="com.vogella.eclipse.preferences.myFirstFontDefinition"
label="vogella Font"
value="Lucida Sans-italic-18">
<description>
Your description for the font
</description>
</fontDefinition>
</extension>
The value for the color can be a COLOR_* constants defined in the SWT class.
You can specify RGB values like 255,0,0.
The value for the font is defined via the following pattern:`fontname-style-height`
The preference can now be changed via the user or via the CSS engine.
To get the current value you can use the IThemeManager.
// Eclipse 4 API
@Inject IThemeManager themeManager;
// Eclipse 3 API
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
ITheme currentTheme = themeManager.getCurrentTheme();
ColorRegistry colorRegistry = currentTheme.getColorRegistry();
Color color = colorRegistry.get("com.vogella.eclipse.preferences.myFirstColorDefinition");
FontRegistry fontRegistry = currentTheme.getFontRegistry();
Font font = fontRegistry.get("com.vogella.eclipse.preferences.myFirstFontDefinition");
2.3. Custom spelling engine
The org.eclipse.ui.workbench.texteditor plug-in provides the option to register a custom spelling engine via the
org.eclipse.ui.workbench.texteditor.spellingEngine extension point.
3. Exercise: Use the generic editor for a custom file type
Eclipse provides the generic editor as the basis for creating text-based editors.
In this exercise, you associate files with the tasks extension with the generic editor.
In the following exercises, you add more and more functionality to this editor.
A tasks file contains key-value pairs, for example:
ID: 1
Summary: Eclipse IDE Training
3.1. Create a new plug-in
Create a new simple plug-in project named com.vogella.ide.editor.tasks.
3.2. Add Manifest dependencies
Open the editor for the MANIFEST.MF file and add the following dependencies via the Dependencies tab using the Add button.
-
org.eclipse.text
-
org.eclipse.ui
-
org.eclipse.ui.editors
-
org.eclipse.ui.genericeditor
-
org.eclipse.ui.workbench.texteditor
-
org.eclipse.jface.text
-
org.eclipse.core.runtime
-
org.eclipse.core.resources
It should look similar to the following (version numbers have been removed, as they change frequently).
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Tasks
Bundle-SymbolicName: com.vogella.ide.editor.tasks;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: VOGELLA
Automatic-Module-Name: com.vogella.ide.editor.tasks
Bundle-RequiredExecutionEnvironment: JavaSE-25
Require-Bundle: org.eclipse.text,
org.eclipse.ui,
org.eclipse.ui.editors,
org.eclipse.ui.genericeditor,
org.eclipse.ui.workbench.texteditor,
org.eclipse.jface.text,
org.eclipse.core.runtime,
org.eclipse.core.resources
3.3. Define content type
Using the MANIFEST.MF editor, open the Extensions tab and press the Add… button.
Select the org.eclipse.core.contenttype.contentTypes extension point.

Press Finish to close the dialog and add the extension.
Right-click on your new entry and select .
Specify a content type for files with the .tasks extension.

This creates an entry in the plugin.xml file, which should look similar to the following listing.
You can see it by clicking the plugin.xml tab of the manifest editor.
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.core.contenttype.contentTypes">
<content-type
file-extensions="tasks"
id="com.vogella.ide.contenttype.tasks"
name="Tasks"
priority="high">
</content-type>
</extension>
</plugin>
3.4. Associate content type with editor
This content type can be associated with a certain editor.
The org.eclipse.ui.editors extension point can be used for this.
Add the org.eclipse.ui.editors extension via the Add… button on the Extensions tab.

Right-click on your org.eclipse.ui.editors entry and select .
Use your content type ID and org.eclipse.ui.genericeditor.GenericEditor as editor ID.

The resulting plugin.xml should now look similar to the following.
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.core.contenttype.contentTypes">
<content-type
file-extensions="tasks"
id="com.vogella.ide.contenttype.tasks"
name="Tasks"
priority="high">
</content-type>
</extension>
<extension
point="org.eclipse.ui.editors">
<editorContentTypeBinding
contentTypeId="com.vogella.ide.contenttype.tasks"
editorId="org.eclipse.ui.genericeditor.GenericEditor">
</editorContentTypeBinding>
</extension>
</plugin>
3.5. Add your plug-in to your product via the feature
Add your new plug-in to your IDE feature or product.
If you are not using features or products, skip this step.
3.6. Test your development
If you are using a product configuration file, start a new runtime Eclipse via your product.
|
Starting via the product updates the associated launch configuration. If you start the runtime Eclipse directly via an unmodified launch configuration, your updated product or feature content is not considered. |
If you are not using features and a product, update the launch configuration directly to ensure your new plug-in is included in the start.
In your runtime Eclipse, ensure that your content type is visible in .

Create a new project (either General or Java project).
In this new project, create a new file with the .tasks extension.
If you open the file, it should open in the generic text editor.

The file uses the icon of the generic editor.
3.7. Optional: Define icon for your content type
Download icons with a size of 16x16 px (and 32x32 px for high-resolution displays) from the Internet or use the Plug-in Image Browser view to copy an existing icon into your project.
If you are reading this text online, you can download the following icons via a right-click in your browser.


Create a folder named icons and copy your icons into this folder.
Add an extension for the org.eclipse.ui.genericeditor.icons extension point to your plugin.xml file and configure the generic editor to use your icon for your content type.
The new entry should look similar to the following:
<extension
point="org.eclipse.ui.genericeditor.icons">
<icon
contentType="com.vogella.ide.contenttype.tasks"
icon="icons/file_type_taskfile.png">
</icon>
</extension>
Ensure that the icons folder is included in the export via the build.properties file.
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.,\
plugin.xml,\
icons/
Start the runtime Eclipse again and validate that your tasks files show your icon.
4. Exercise: Implementing syntax highlighting
In this exercise, you implement syntax highlighting for your tasks file editor.
You continue to work on the com.vogella.ide.editor.tasks plug-in.
4.1. Implement syntax highlighting
Implement the following class to define an IRule.
package com.vogella.ide.editor.tasks;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
public class PropertyNameRule implements IRule {
private final Token token;
public PropertyNameRule(Token token) {
this.token = token;
}
@Override
public IToken evaluate(ICharacterScanner scanner) {
int c = scanner.read();
int count = 1;
while (c != ICharacterScanner.EOF) {
if (c == ':') {
return token;
}
if ('\n' == c || '\r' == c) {
break;
}
count++;
c = scanner.read();
}
// put the scanner back to the original position if no match
for (int i = 0; i < count; i++) {
scanner.unread();
}
return Token.UNDEFINED;
}
}
Implement the following class, which is used as the presentation reconciler for your editor.
package com.vogella.ide.editor.tasks;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
public class PropertiesReconciler extends PresentationReconciler {
private final TextAttribute tagAttribute = new TextAttribute(
Display.getCurrent().getSystemColor(SWT.COLOR_RED));
public PropertiesReconciler() {
RuleBasedScanner scanner = new RuleBasedScanner();
IRule rule = new PropertyNameRule(new Token(tagAttribute));
scanner.setRules(rule);
DefaultDamagerRepairer dr = new DefaultDamagerRepairer(scanner);
this.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
this.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
}
}
Add an extension for the org.eclipse.ui.genericeditor.presentationReconcilers extension point to the plugin.xml file of the com.vogella.ide.editor.tasks plug-in.
<extension
point="org.eclipse.ui.genericeditor.presentationReconcilers">
<presentationReconciler
class="com.vogella.ide.editor.tasks.PropertiesReconciler"
contentType="com.vogella.ide.contenttype.tasks">
</presentationReconciler>
</extension>
4.2. Test your implementation
Restart your runtime Eclipse.
Open a .tasks file and enter a few properties, for example:
ID: 1
Summary: Eclipse IDE Training
Description: Learn how to extend the Eclipse IDE
Done: false
Duedate: 2026-12-01
Dependent:
The keys are shown in red.

4.3. Optional: Show the keys in bold
TextAttribute also accepts a background color and a font style.
To make the keys stand out more, change the tagAttribute field of PropertiesReconciler to also use a bold font.
private final TextAttribute tagAttribute = new TextAttribute(
Display.getCurrent().getSystemColor(SWT.COLOR_RED), null, SWT.BOLD);
Reopen your .tasks file to see the bold keys.
5. Exercise: Implementing syntax highlighting using TextMate
In this exercise, you implement syntax highlighting for shell files using a TextMate grammar.
| This exercise uses TM4E, the TextMate support for Eclipse. |
5.1. Add TM4E to target file
Open your target definition file and add the following location to it.
<location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
<repository location="https://download.eclipse.org/tm4e/releases/latest/"/>
<unit id="org.eclipse.tm4e.feature.feature.group"/>
</location>
5.2. Add TM4E feature to product
To ensure that the TM4E plug-ins are present at runtime, add the org.eclipse.tm4e.feature feature to your product and start the runtime Eclipse via the product.
5.3. Implement plug-in
Create a new simple plug-in project called com.vogella.ide.editor.shell.
Open the editor for the MANIFEST.MF file and add the following dependencies via the Dependencies tab using the Add button.
-
org.eclipse.text
-
org.eclipse.ui
-
org.eclipse.ui.editors
-
org.eclipse.ui.genericeditor
-
org.eclipse.ui.workbench.texteditor
-
org.eclipse.jface.text
-
org.eclipse.core.runtime
-
org.eclipse.core.resources
-
org.eclipse.tm4e.registry
5.4. Create syntax description
Create a new folder called syntaxes and in it a file named Shell-Unix-Bash.json with the following content.
{
"scopeName": "source.shell",
"name": "Shell Script",
"fileTypes": ["sh", "bash"],
"patterns": [
{
"name": "comment.line.number-sign.shell",
"match": "#.*$"
},
{
"name": "string.quoted.double.shell",
"begin": "\"",
"end": "\"",
"patterns": [
{
"name": "variable.other.shell",
"match": "\\$[a-zA-Z_][a-zA-Z0-9_]*"
}
]
},
{
"name": "string.quoted.single.shell",
"begin": "'",
"end": "'"
},
{
"name": "variable.other.shell",
"match": "\\$[a-zA-Z_][a-zA-Z0-9_]*"
},
{
"name": "keyword.control.shell",
"match": "\\b(if|then|else|fi|for|while|do|done|case|esac)\\b"
},
{
"name": "keyword.operator.shell",
"match": "\\b(and|or|not)\\b"
},
{
"name": "support.function.builtin.shell",
"match": "\\b(echo|cd|exit|pwd|read)\\b"
}
],
"repository": {},
"uuid": "eae8d676-1b23-11e0-ac64-0800200c9a66"
}
5.5. Create plugin.xml file
Your plug-in has no plugin.xml file yet.
Select the Overview tab in the manifest editor and click the Extensions link to create it.
5.6. Create content type and associate it with the generic editor
Create the following extensions to define a new content type and associate it with the generic editor.
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.core.contenttype.contentTypes">
<content-type
base-type="org.eclipse.core.runtime.text"
file-extensions="shell"
id="com.vogella.ide.editor.shell.content-type"
name="Shell"
priority="normal">
</content-type>
</extension>
<extension
point="org.eclipse.ui.editors">
<editorContentTypeBinding
contentTypeId="com.vogella.ide.editor.shell.content-type"
editorId="org.eclipse.ui.genericeditor.GenericEditor">
</editorContentTypeBinding>
</extension>
</plugin>
5.7. Register your grammar and content type with TM4E
Now register your grammar with TM4E and bind it to your content type.
Add the following extension in addition to the existing plugin.xml entries.
<extension
point="org.eclipse.tm4e.registry.grammars">
<grammar
path="syntaxes/Shell-Unix-Bash.json"
scopeName="source.shell">
</grammar>
<scopeNameContentTypeBinding
contentTypeId="com.vogella.ide.editor.shell.content-type"
scopeName="source.shell">
</scopeNameContentTypeBinding>
</extension>
5.8. Test
Include your new plug-in in your launch configuration, for example, via your feature, and start the runtime Eclipse via your product.
Create a new general project and in it an example.shell file with the following content.
The editor should show syntax highlighting for this file.
#!/bin/bash
# A simple script to demonstrate folding
#region Setup
echo "Setting up environment..."
# Declare variables
var1="Hello"
var2="World"
#endregion
#region Main logic
if [[ $var1 == "Hello" && $var2 == "World" ]]; then
echo "Both variables are correctly set."
else
echo "Variables are not set correctly."
fi
# Loop example
for i in {1..5}; do
echo "Loop iteration: $i"
done
#endregion
#region Cleanup
echo "Cleaning up..."
#endregion
The editor shows comments, strings, variables and keywords in different colors.

5.9. Adding more language configuration
A VS Code language configuration defines, for example, bracket pairs, auto-closing pairs and folding markers for a language. TM4E can bind such a language configuration to a content type.
Create a folder named language-configurations in your plug-in.
Create a file named shell.language-configuration.json in this folder with the following content.
It defines # as the line comment and the #region and #endregion comments as folding markers.
{
"comments": {
"lineComment": "#"
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"folding": {
"markers": {
"start": "^\\s*#\\s*region\\b",
"end": "^\\s*#\\s*endregion\\b"
}
}
}
Register this language configuration via the following extension in your plugin.xml file.
<extension
point="org.eclipse.tm4e.languageconfiguration.languageConfigurations">
<languageConfiguration
contentTypeId="com.vogella.ide.editor.shell.content-type"
path="language-configurations/shell.language-configuration.json">
</languageConfiguration>
</extension>
Add the syntaxes and language-configurations folders to the bin.includes of your build.properties file, otherwise they are missing in the exported plug-in.
bin.includes = META-INF/,\
.,\
plugin.xml,\
syntaxes/,\
language-configurations/
Restart your runtime IDE and open your file.
You should see folding icons for the #region blocks.

5.10. Checking the dark theme
TM4E supports the standard Eclipse light and dark themes. Switch your runtime IDE to the dark theme via the preference page and restart it.
Your editor automatically uses colors that fit the selected theme.

5.11. Optional: Switch theme at runtime
TM4E provides multiple themes. Right-click in the editor ruler and select another one via the Switch to Theme… entry.

5.12. Optional: Add syntax highlighting for an additional language
Now that you know how to add syntax highlighting for a language, add syntax highlighting for *.ini files.
You can use the following grammar for this purpose: https://github.com/textmate/ini.tmbundle/blob/master/Syntaxes/Ini.plist
The file to which you copy this content must have the .tmLanguage extension.
6. Exercise: Allow the user to customize the colors
In the previous exercise, you implemented syntax highlighting for a tasks file editor using a hard-coded color. This is not optimal, as it prevents the user from customizing this color.
In this exercise, you extend the com.vogella.ide.editor.tasks plug-in to allow the user to customize this color.
6.1. Define colors
Use the Extensions tab on the manifest editor to add an extension for the org.eclipse.ui.themes extension point.
Right-click on the generated entry, select and use:
-
id: com.vogella.ide.editor.tasks.settings
-
label: Tasks settings

Right-click on the org.eclipse.ui.themes entry, select and use:
-
id: com.vogella.ide.editor.tasks.key
-
label: Task key color
-
value: 255,0,0
-
categoryId: com.vogella.ide.editor.tasks.settings

The plugin.xml content should look similar to the following:
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.core.contenttype.contentTypes">
<content-type
file-extensions="tasks"
id="com.vogella.ide.contenttype.tasks"
name="Tasks"
priority="high">
</content-type>
</extension>
<extension
point="org.eclipse.ui.editors">
<editorContentTypeBinding
contentTypeId="com.vogella.ide.contenttype.tasks"
editorId="org.eclipse.ui.genericeditor.GenericEditor">
</editorContentTypeBinding>
</extension>
<extension
point="org.eclipse.ui.genericeditor.presentationReconcilers">
<presentationReconciler
class="com.vogella.ide.editor.tasks.PropertiesReconciler"
contentType="com.vogella.ide.contenttype.tasks">
</presentationReconciler>
</extension>
<extension
point="org.eclipse.ui.themes">
<colorDefinition
categoryId="com.vogella.ide.editor.tasks.settings"
id="com.vogella.ide.editor.tasks.key"
label="Task key color"
value="255,0,0">
</colorDefinition>
<themeElementCategory
id="com.vogella.ide.editor.tasks.settings"
label="Tasks settings">
</themeElementCategory>
</extension>
</plugin>
6.3. Use colors for your syntax highlighting
Read the color from the color registry of the current theme and use it in your PropertiesReconciler.
package com.vogella.ide.editor.tasks;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.swt.graphics.Color;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;
public class PropertiesReconciler extends PresentationReconciler {
public PropertiesReconciler() {
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
ITheme currentTheme = themeManager.getCurrentTheme();
ColorRegistry colorRegistry = currentTheme.getColorRegistry();
Color color = colorRegistry.get("com.vogella.ide.editor.tasks.key");
TextAttribute tagAttribute = new TextAttribute(color);
RuleBasedScanner scanner = new RuleBasedScanner();
IRule rule = new PropertyNameRule(new Token(tagAttribute));
scanner.setRules(rule);
DefaultDamagerRepairer dr = new DefaultDamagerRepairer(scanner);
this.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
this.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
}
}
6.4. Test your changes
Open . Search for your color and change it.
Close your editor and reopen it. Validate that your new color is used.
6.5. Use a preference listener to update the color
Preferences are persisted user settings. A preference listener is notified about changes in a preference node.
In your PropertiesReconciler, override the install method and listen to changes in the org.eclipse.ui.workbench node, which persists the color.
Store the listener in a field and remove it again in the uninstall method, otherwise every closed editor leaves a listener behind.
private IPreferenceChangeListener listener;
@Override
public void install(ITextViewer viewer) {
super.install(viewer);
listener = event -> {
// TODO update your rules with a rule using the new color
viewer.invalidateTextPresentation();
};
InstanceScope.INSTANCE.getNode("org.eclipse.ui.workbench").addPreferenceChangeListener(listener);
}
@Override
public void uninstall() {
if (listener != null) {
InstanceScope.INSTANCE.getNode("org.eclipse.ui.workbench").removePreferenceChangeListener(listener);
listener = null;
}
super.uninstall();
}
Solve the TODO and check that the color is updated without reopening the editor.
|
To find the preference node for a preference, you can use the preference spy. Open the view via . Toggle the trace for preferences. Afterwards, change a color and see its data. ![]() |
The following listing shows a possible solution.
package com.vogella.ide.editor.tasks;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.swt.graphics.Color;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;
public class PropertiesReconciler extends PresentationReconciler {
private ColorRegistry colorRegistry;
private RuleBasedScanner scanner;
private IRule rule;
private IPreferenceChangeListener listener;
@Override
public void install(ITextViewer viewer) {
super.install(viewer);
listener = event -> {
updateRule();
viewer.invalidateTextPresentation();
};
getWorkbenchPreferences().addPreferenceChangeListener(listener);
}
@Override
public void uninstall() {
if (listener != null) {
getWorkbenchPreferences().removePreferenceChangeListener(listener);
listener = null;
}
super.uninstall();
}
private IEclipsePreferences getWorkbenchPreferences() {
return InstanceScope.INSTANCE.getNode("org.eclipse.ui.workbench");
}
private void updateRule() {
Color color = colorRegistry.get("com.vogella.ide.editor.tasks.key");
TextAttribute tagAttribute = new TextAttribute(color);
rule = new PropertyNameRule(new Token(tagAttribute));
scanner.setRules(new IRule[] { rule });
}
public PropertiesReconciler() {
IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
ITheme currentTheme = themeManager.getCurrentTheme();
colorRegistry = currentTheme.getColorRegistry();
scanner = new RuleBasedScanner();
updateRule();
DefaultDamagerRepairer dr = new DefaultDamagerRepairer(scanner);
this.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
this.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
}
}
7. Exercise: Implementing content assist for task properties
In this exercise, you implement content assist (code completion) for your .tasks files.
As discussed earlier, these files contain key-value pairs, separated by a colon.
Properties are proposed if the cursor is located at the beginning of a line.
7.1. Add the content assists extension
Add an extension for the org.eclipse.ui.genericeditor.contentAssistProcessors extension point in the manifest file of your com.vogella.ide.editor.tasks plug-in.
<extension
point="org.eclipse.ui.genericeditor.contentAssistProcessors">
<contentAssistProcessor
class="com.vogella.ide.editor.tasks.TodoPropertiesContentAssistProcessor"
contentType="com.vogella.ide.contenttype.tasks">
</contentAssistProcessor>
</extension>
The implementation of the TodoPropertiesContentAssistProcessor should look similar to the following code.
package com.vogella.ide.editor.tasks;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
public class TodoPropertiesContentAssistProcessor implements IContentAssistProcessor {
// public as used later by other code
public static final List<String> PROPOSALS = Arrays.asList("ID:", "Summary:", "Description:", "Done:", "Duedate:", "Dependent:");
@Override
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
IDocument document = viewer.getDocument();
try {
int lineOfOffset = document.getLineOfOffset(offset);
int lineOffset = document.getLineOffset(lineOfOffset);
// only propose properties at the beginning of a line
if (offset != lineOffset) {
return new ICompletionProposal[0];
}
} catch (BadLocationException e) {
// ignore here and just continue
}
String text = document.get();
return PROPOSALS.stream()
.filter(proposal -> !text.contains(proposal))
.map(proposal -> new CompletionProposal(proposal, offset, 0, proposal.length()))
.toArray(ICompletionProposal[]::new);
}
@Override
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
return null;
}
@Override
public char[] getCompletionProposalAutoActivationCharacters() {
return null;
}
@Override
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
@Override
public String getErrorMessage() {
return null;
}
@Override
public IContextInformationValidator getContextInformationValidator() {
return null;
}
}
7.2. Test your implementation
Start the IDE, create an empty testing.tasks file and press Ctrl+Space to activate content assist.
The proposals list all keys that the file does not contain yet.

7.3. Optional exercise: Implement a slow content assist processor
The generic editor uses asynchronous code completion by default, so the user interface does not block even if one of your proposal computers is slow.
Test this by adding a delay to the beginning of your computeCompletionProposals method.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Ensure that the editor remains usable while content assist is computing its proposals.
Afterwards, remove the delay again.
8. Improved code completion
8.1. Optional exercise: Add prefix completion
The IDocument interface offers a lot of utilities to parse and modify its contents.
Extend your TodoPropertiesContentAssistProcessor#computeCompletionProposals method so that it matches on prefixes.
For example, if you type D, you should see all proposals starting with D.

Solution example
@Override
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
IDocument document = viewer.getDocument();
try {
int lineOfOffset = document.getLineOfOffset(offset);
int lineOffset = document.getLineOffset(lineOfOffset);
int lineTextLength = offset - lineOffset;
String lineStartToOffsetValue = document.get(lineOffset, lineTextLength).toLowerCase();
String text = document.get();
return PROPOSALS.stream()
.filter(proposal -> !text.contains(proposal)
&& proposal.toLowerCase().startsWith(lineStartToOffsetValue))
.map(proposal -> new CompletionProposal(proposal, lineOffset, lineTextLength, proposal.length()))
.toArray(ICompletionProposal[]::new);
} catch (BadLocationException e) {
e.printStackTrace();
}
return new ICompletionProposal[0];
}
8.2. Optional exercise: Enable auto-activation of your content assist
Currently, your content assist only shows its proposals if the user presses Ctrl+Space. This might not be obvious for the user.
IContentAssistProcessor allows you to modify this behavior via its getCompletionProposalAutoActivationCharacters method.
Implement it so that any letter activates the content assist.
Solution example
@Override
public char[] getCompletionProposalAutoActivationCharacters() {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
return str.toCharArray();
}
9. Exercise: Implementing another content assist using other information
It is possible to register multiple content assist processors for the same content type.
In this exercise, you add another content assist processor to your editor. This processor allows you to set values for the Dependent: property, which models that a task depends on another task.
9.1. Implement and register a new content assist processor
Create the DependentTodoContentAssistProcessor class implementing the IContentAssistProcessor interface.
Register this class via the plugin.xml file as an extension to the org.eclipse.ui.genericeditor.contentAssistProcessors extension point.
Solution example
<extension
point="org.eclipse.ui.genericeditor.contentAssistProcessors">
<contentAssistProcessor
class="com.vogella.ide.editor.tasks.TodoPropertiesContentAssistProcessor"
contentType="com.vogella.ide.contenttype.tasks">
</contentAssistProcessor>
</extension>
<extension
point="org.eclipse.ui.genericeditor.contentAssistProcessors">
<contentAssistProcessor
class="com.vogella.ide.editor.tasks.DependentTodoContentAssistProcessor"
contentType="com.vogella.ide.contenttype.tasks">
</contentAssistProcessor>
</extension>
For accessing the active editor, create the following Util class.
package com.vogella.ide.editor.tasks;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
public class Util {
private Util() {
// only helper
}
public static IEditorPart getActiveEditor() {
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow();
if (null == activeWorkbenchWindow) {
activeWorkbenchWindow = workbench.getWorkbenchWindows()[0];
}
IWorkbenchPage activePage = activeWorkbenchWindow.getActivePage();
if (activePage == null) {
return null;
}
return activePage.getActiveEditor();
}
}
The implementation of the DependentTodoContentAssistProcessor should look similar to this:
package com.vogella.ide.editor.tasks;
import static com.vogella.ide.editor.tasks.Util.getActiveEditor;
import java.util.Arrays;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
public class DependentTodoContentAssistProcessor implements IContentAssistProcessor {
@Override
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
IDocument document = viewer.getDocument();
IEditorPart activeEditor = getActiveEditor();
if (activeEditor != null) {
IEditorInput editorInput = activeEditor.getEditorInput();
IResource adapter = editorInput.getAdapter(IResource.class);
if (adapter == null) {
// file outside the workspace
return new ICompletionProposal[0];
}
IContainer parent = adapter.getParent();
try {
int lineOfOffset = document.getLineOfOffset(offset);
int lineOffset = document.getLineOffset(lineOfOffset);
String lineProperty = document.get(lineOffset, offset - lineOffset);
// content assist is only offered in the dependent line
if (lineProperty.startsWith("Dependent:")) {
IResource[] members = parent.members();
// only take resources with the "tasks" file extension and skip the current resource
return Arrays.asList(members).stream().filter(
res -> !adapter.getName().equals(res.getName()) && "tasks".equals(res.getFileExtension()))
.map(res -> new CompletionProposal(res.getName(), offset, 0, res.getName().length()))
.toArray(ICompletionProposal[]::new);
}
} catch (CoreException | BadLocationException e) {
// no proposals in this case
}
}
return new ICompletionProposal[0];
}
@Override
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
return null;
}
@Override
public char[] getCompletionProposalAutoActivationCharacters() {
return null;
}
@Override
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
@Override
public String getErrorMessage() {
return null;
}
@Override
public IContextInformationValidator getContextInformationValidator() {
return null;
}
}
9.2. Validate
Start the IDE, ensure that at least two .tasks files are available and open one of these files.
Then press Ctrl+Space right after the Dependent: property to activate content assist.
10. Exercise: Reacting to document changes
An IDocumentSetupParticipant is called when a document is set up, which allows you to react to changes of this document.
You can register an implementation via the org.eclipse.core.filebuffers.documentSetup extension point for a content type.
In this exercise, you use it to add markers to the Problems view for missing keys in a .tasks file.
10.1. Implement and register your IDocumentSetupParticipant
The TodoMarkerDocumentSetup registers an IDocumentListener, which applies markers to the file of the changed document.
package com.vogella.ide.editor.tasks;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.IDocumentSetupParticipant;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ICoreRunnable;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
public class TodoMarkerDocumentSetup implements IDocumentSetupParticipant {
private static final String TODO_PROPERTY = "todoProperty";
@Override
public void setup(IDocument document) {
document.addDocumentListener(new IDocumentListener() {
private Job markerJob;
@Override
public void documentChanged(DocumentEvent event) {
IFile file = getFile(event.getDocument()); (1)
if (file != null) {
String text = event.getDocument().get(); // snapshot, the job runs later
if (markerJob != null) {
markerJob.cancel();
}
markerJob = Job.create("Adding Marker", (ICoreRunnable) monitor -> createMarker(text, file)); (2)
markerJob.setUser(false);
markerJob.setPriority(Job.DECORATE);
// set a delay before reacting to user action to handle continuous typing
markerJob.schedule(500); (3)
}
}
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
// not needed
}
});
}
// the document belongs to a file buffer, the active editor may show another file
private IFile getFile(IDocument document) {
ITextFileBuffer buffer = ITextFileBufferManager.DEFAULT.getTextFileBuffer(document);
if (buffer == null || buffer.getLocation() == null) {
return null;
}
return FileBuffers.getWorkspaceFileAtLocation(buffer.getLocation());
}
private void createMarker(String docText, IResource adapter) throws CoreException {
List<IMarker> markers = Arrays.asList(adapter.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE));
for (String todoProperty : TodoPropertiesContentAssistProcessor.PROPOSALS) {
Optional<IMarker> findAny = markers.stream()
.filter(m -> todoProperty.equals(m.getAttribute(TODO_PROPERTY, ""))).findAny();
if (docText.contains(todoProperty) && findAny.isPresent()) {
findAny.get().delete();
} else if (!docText.contains(todoProperty) && !findAny.isPresent()) {
IMarker marker = adapter.createMarker(IMarker.PROBLEM);
marker.setAttribute(IMarker.MESSAGE, todoProperty + " property is not set");
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
marker.setAttribute(IMarker.LOCATION, "Missing line");
marker.setAttribute(TODO_PROPERTY, todoProperty);
marker.setAttribute(IMarker.LINE_NUMBER, 1);
}
}
}
}
| 1 | The file is determined via the file buffer of the document, as the active editor might show a different file. |
| 2 | The markers are created in a Job, so that the UI thread is not blocked. |
| 3 | The job starts with a delay, so that another document change can cancel it and unnecessary marker updates are avoided. |
Add the following extension to your plugin.xml file.
<extension
point="org.eclipse.core.filebuffers.documentSetup">
<participant
class="com.vogella.ide.editor.tasks.TodoMarkerDocumentSetup"
contentTypeId="com.vogella.ide.contenttype.tasks">
</participant>
</extension>
10.2. Validate
Start the IDE, create a testing.tasks file and enter only the ID and Summary keys.
After a short delay, the Problems view shows an info marker for each missing key.

11. Exercise: Implement hyperlinking for your tasks
Eclipse editors support navigation between references to other files. Source editors typically open the referenced file if you hold Ctrl and click on the reference.
The *.tasks files can point via the Dependent: property to another *.tasks file inside the same project. In this exercise, you allow the user to navigate between dependent task files in your editor.
11.1. Add manifest dependencies
Add org.eclipse.ui.ide as a plug-in dependency to com.vogella.ide.editor.tasks.
11.2. Implement hyperlinking for dependent task files
Create the following IHyperlink implementation in the com.vogella.ide.editor.tasks.hyperlink package.
package com.vogella.ide.editor.tasks.hyperlink;
import org.eclipse.core.resources.IFile;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
public class ResourceHyperlink implements IHyperlink {
private final IRegion region;
private final String hyperlinkText;
private final IFile resource;
public ResourceHyperlink(IRegion region, String hyperlinkText, IFile resource) {
this.region = region;
this.hyperlinkText = hyperlinkText;
this.resource = resource;
}
@Override
public IRegion getHyperlinkRegion() {
return region;
}
@Override
public String getTypeLabel() {
return null;
}
@Override
public String getHyperlinkText() {
return hyperlinkText;
}
@Override
public void open() {
IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
try {
IDE.openEditor(activePage, resource);
} catch (PartInitException e) {
e.printStackTrace();
}
}
}
Create the following IHyperlinkDetector implementation.
package com.vogella.ide.editor.tasks.hyperlink;
import static com.vogella.ide.editor.tasks.Util.getActiveEditor;
import java.util.Arrays;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
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.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
public class DependentTodoHyperlinkDetector extends AbstractHyperlinkDetector {
private static final String DEPENDENT_PROPERTY = "Dependent:";
@Override
public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
IDocument document = textViewer.getDocument();
IEditorPart activeEditor = getActiveEditor();
if (activeEditor != null) {
IEditorInput editorInput = activeEditor.getEditorInput();
IResource adapter = editorInput.getAdapter(IResource.class);
if (adapter == null) {
// file outside the workspace
return null;
}
IContainer parent = adapter.getParent();
try {
int offset = region.getOffset();
IRegion lineInformationOfOffset = document.getLineInformationOfOffset(offset);
String lineContent = document.get(lineInformationOfOffset.getOffset(),
lineInformationOfOffset.getLength());
// hyperlinks are only offered in the dependent line
if (lineContent.startsWith(DEPENDENT_PROPERTY)) {
String dependentResourceName = lineContent.substring(DEPENDENT_PROPERTY.length()).trim();
Region targetRegion = new Region(lineInformationOfOffset.getOffset() + DEPENDENT_PROPERTY.length(),
lineInformationOfOffset.getLength() - DEPENDENT_PROPERTY.length());
IResource[] members = parent.members();
// link to the sibling file named in the dependent line
IHyperlink[] hyperlinks = Arrays.stream(members)
.filter(res -> res instanceof IFile && dependentResourceName.equals(res.getName()))
.map(res -> new ResourceHyperlink(targetRegion, res.getName(), (IFile) res))
.toArray(IHyperlink[]::new);
if (hyperlinks.length > 0) {
return hyperlinks;
}
}
} catch (CoreException | BadLocationException e) {
e.printStackTrace();
}
}
// the contract allows only null or a non-empty array
return null;
}
}
Register the DependentTodoHyperlinkDetector class in the plugin.xml file like this:
<extension
point="org.eclipse.ui.workbench.texteditor.hyperlinkDetectors">
<hyperlinkDetector
activate="true"
class="com.vogella.ide.editor.tasks.hyperlink.DependentTodoHyperlinkDetector"
id="com.vogella.ide.editor.tasks.hyperlinkDetector"
name="Hyperlink to other tasks files"
targetId="org.eclipse.ui.genericeditor.GenericEditor">
</hyperlinkDetector>
</extension>
11.3. Validate
Create at least two *.tasks files and use the Dependent: property to point from one to the other.
Dependent: Training.tasks
Hold Ctrl and move the mouse over the file name. The file name is shown as a link. Click it to open the linked file.

12. Exercise: Implement a hyperlink detector for your company name
In this exercise, you create a hyperlink detector for the vogella keyword, which works in all text editors.
12.1. Create project and add dependencies
Create a new plug-in project called com.vogella.ide.editor.companylink.
Add the following entries as plug-in dependencies:
-
org.eclipse.ui
-
org.eclipse.jface
-
org.eclipse.jface.text
-
org.eclipse.ui.workbench.texteditor
12.2. Add hyperlink detector
Add the org.eclipse.ui.workbench.texteditor.hyperlinkDetectors extension with a unique ID, a descriptive name and org.eclipse.ui.DefaultTextEditor as the target ID.
This target covers all editors based on the default text editor configuration, including the Java editor.
Create the following two classes to determine the hyperlink and to react to it.
package com.vogella.ide.editor.companylink;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.swt.program.Program;
public class VogellaHyperlink implements IHyperlink {
private final IRegion fUrlRegion;
public VogellaHyperlink(IRegion urlRegion) {
fUrlRegion = urlRegion;
}
@Override
public IRegion getHyperlinkRegion() {
return fUrlRegion;
}
@Override
public String getTypeLabel() {
return null;
}
@Override
public String getHyperlinkText() {
return "Open vogella website";
}
@Override
public void open() {
Program.launch("https://www.vogella.com/");
}
}
package com.vogella.ide.editor.companylink;
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;
public class VogellaHyperlinkDetector extends AbstractHyperlinkDetector {
@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("vogella");
if (index != -1) {
// detect region containing keyword
IRegion targetRegion = new Region(lineRegion.getOffset() + index, "vogella".length());
if (targetRegion.getOffset() <= offset
&& targetRegion.getOffset() + targetRegion.getLength() > offset) {
return new IHyperlink[] { new VogellaHyperlink(targetRegion) };
}
}
return null;
}
}
12.3. Validate
Start your plug-in and add vogella to a file in one of the text editors, for example, the Java editor. Hold Ctrl and click on vogella. This should open the https://www.vogella.com/ website in an external browser.
13. Exercise: Add code mining information to your task files
Eclipse text editors can show additional information, called code minings, inline in the text. This information is not saved in the file but helps the user to better understand the content. You can register actions on these minings, so that the user can trigger actions directly from the text editor.
In this exercise, you implement code minings for your task editor to show additional information and to allow the user to perform an action via the minings.
13.1. Implement and register code mining
Implement the following class, which creates a line header annotation.
package com.vogella.ide.editor.tasks;
import java.util.concurrent.CompletableFuture;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.codemining.ICodeMiningProvider;
import org.eclipse.jface.text.codemining.LineHeaderCodeMining;
public class TaskCodeMining extends LineHeaderCodeMining {
public TaskCodeMining(int beforeLineNumber, IDocument document, ICodeMiningProvider provider)
throws BadLocationException {
super(beforeLineNumber, document, provider);
}
@Override
protected CompletableFuture<Void> doResolve(ITextViewer viewer, IProgressMonitor monitor) {
return CompletableFuture.runAsync(() -> setLabel("This is additional information about the tasks"));
}
}
Create the following implementation of an ICodeMiningProvider.
package com.vogella.ide.editor.tasks;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.codemining.ICodeMining;
import org.eclipse.jface.text.codemining.ICodeMiningProvider;
public class TaskCodeMiningProvider implements ICodeMiningProvider {
@Override
public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer,
IProgressMonitor monitor) {
return CompletableFuture.supplyAsync(() -> {
List<ICodeMining> minings = new ArrayList<>();
IDocument document = viewer.getDocument();
try {
minings.add(new TaskCodeMining(0, document, this));
} catch (BadLocationException e) {
e.printStackTrace();
}
return minings;
});
}
@Override
public void dispose() {
}
}
Register the default CodeMiningReconciler for your content type via the org.eclipse.ui.genericeditor.reconcilers extension.
<extension
point="org.eclipse.ui.genericeditor.reconcilers">
<reconciler
class="org.eclipse.jface.text.codemining.CodeMiningReconciler"
contentType="com.vogella.ide.contenttype.tasks">
</reconciler>
</extension>
Now add the following extension to your plugin.xml file.
<extension
point="org.eclipse.ui.workbench.texteditor.codeMiningProviders">
<codeMiningProvider
class="com.vogella.ide.editor.tasks.TaskCodeMiningProvider"
id="com.vogella.ide.editor.tasks.codeMiningProvider"
label="Show additional task info">
<enabledWhen>
<with variable="editorInput">
<adapt type="org.eclipse.core.resources.IFile">
<test property="org.eclipse.core.resources.contentTypeId" value="com.vogella.ide.contenttype.tasks" />
</adapt>
</with>
</enabledWhen>
</codeMiningProvider>
</extension>
14. Optional exercise: Add an action to the code mining
Override getAction in TaskCodeMining to allow the user to trigger an action by clicking on the mining.
package com.vogella.ide.editor.tasks;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.codemining.ICodeMiningProvider;
import org.eclipse.jface.text.codemining.LineHeaderCodeMining;
import org.eclipse.swt.events.MouseEvent;
public class TaskCodeMining extends LineHeaderCodeMining {
private ITextViewer viewer;
public TaskCodeMining(int beforeLineNumber, IDocument document, ICodeMiningProvider provider)
throws BadLocationException {
super(beforeLineNumber, document, provider);
}
@Override
protected CompletableFuture<Void> doResolve(ITextViewer viewer, IProgressMonitor monitor) {
this.viewer = viewer;
return CompletableFuture.runAsync(() -> setLabel("This is additional information about the tasks"));
}
@Override
public Consumer<MouseEvent> getAction() {
return e -> showMessageDialog();
}
private void showMessageDialog() {
MessageDialog.openInformation(viewer.getTextWidget().getShell(), "Clicked",
"You clicked on the code mining annotation");
}
}
15. Optional exercise: Extend your code mining
Show live information in the header instead of a static text.
For example, show how many of the task properties are already set.
For this, add a private final IDocument document; field to TaskCodeMining, assign the document passed to the constructor to it, and replace the setLabel call in doResolve with the following code.
TodoPropertiesContentAssistProcessor.PROPOSALS is the list of property names from your content assist processor, so make it accessible from the code mining class.
String text = document.get();
long filled = TodoPropertiesContentAssistProcessor.PROPOSALS.stream()
.filter(text::contains)
.count();
setLabel(filled + " of " + TodoPropertiesContentAssistProcessor.PROPOSALS.size() + " task properties set");
As a further extension, add a code mining to each line in your editor, which shows the number of characters of the property value.
16. Optional exercise: Review the JDT code minings
The Java editor of the Eclipse IDE provides code minings, for example, for the number of references and implementations of a type or method. Activate them via and open a Java file.
Review their implementation in the org.eclipse.jdt.ui plug-in in the eclipse.jdt.ui repository.
17. Excurse: Implementing your custom editor
While it is recommended to reuse the generic editor instead defining a new one, this is still supported. This chapter gives an explanation how this can be done.
17.1. IEditorPart and EditorPart
To define a new editor for the Eclipse IDE, you typically:
* Create an IEditorInput class
* Define an extension for the org.eclipse.ui.editors extension point
* Implement a class extending IEditorPart
IEditorInput serves as the model for the editor.
Eclipse will buffer IEditorInput objects therefore this object should be relatively small.
It is supposed to be a light-weight representation of the model.
For example, the Eclipse IDE uses IEditorInput objects to identify files without handling with the complete file.
The equals() of IEditorInput define the identity of the editor, e.g., it will be used to determine if an editor is already open or not.
The editor receives the IEditorSite and the IEditorInput in the init() method.
It must set the input via the setInput() method and the side via the setSite() method.
init() is called before createPartControl() (which creates the user interface).
Therefore, you can use the input during your UI creation.
If you define your own perspective, you can enable the editor area via the following code in your perspective implementation.
import org.eclipse.ui.IPageLayout;
public class Perspective implements IPerspectiveFactory {
public void createInitialLayout(IPageLayout layout) {
//layout.setEditorAreaVisible(false);
layout.setFixed(true);
}
}
17.2. Setting the editor title and tooltip
By default, the editor will use the tooltip and title from the IEditorInput.
Therefore, you may want to change the title and tooltip in your Editor.
Use setPartName() to set the title of the Editor and getTitleToolTip() for setting the tooltip.
See Bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=107772 for details on the tooltip.
17.3. Saving the editor content
The method isDirty() determines if the editor contains modified data.
For inform the workbench about changes in the dirty state you fired an event.
firePropertyChange(IEditorPart.PROP.DIRTY);
18. Links and literature
18.1. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting


