Extending the C/C++ Editor in NetBeans IDE 6.0 to Provide Mark Occurrences Highlighting
Contributed and maintained by
November 2007 [Revision number: V6.0-1] This publication is applicable
to NetBeans IDE 6.0 release
In this tutorial you will see how to extend C/C++ editor functionality.
We will create a NetBeans module for code highlighting in the editor using NetBeans editor highlighting SPI.
And we will use the C/C++ Reference API to retrieve information from language model.
For more information about working with C/C++ applications in the NetBeans IDE, see the C/C++ Applications Learning Trail page on the
NetBeans web site.
Tutorial Requirements
Before you proceed, make sure you review the requirements in this section.
Prerequisites
This tutorial assumes that you have some basic knowledge of using IDEs and programming experience
with Java.
Software Needed for This Tutorial
Before you begin, you need to install NetBeans 6.0.
You will need both C/C++ and Java SE support, so the best choice
is to select the Download All option and exclude
all modules except the Base IDE, Java SE, and C/C++ packs during installation.
Preparing the Projects
We will need two projects for this tutorial. One is a NetBeans module for
the source code of our plugin. And another
is C++ project to test its functionality.
Creating the NetBeans Plugin Module
Choose File > New Project. In the New Project wizard,
select NetBeans Modules under Categories and Module under Projects. Click Next.
On the Name and Location page, type MarkOccurrences in the
Project Name field and set Project Location to an appropriate folder on your disk.
If they are not selected, select Standalone Module and Set as Main Project. Click Next.
On the Basic Module Configuration page, type
org.netbeans.modules.markoccurrences in the Code Name Base
field. Click Finish.
We will need certain dependencies in this project. In the
Projects window, add each library listed in the screenshot by
right-clicking the Libraries node and selecting the library
in the Add Module Dependency dialog box. C/C++ modules APIs are under
development, so you will need to select Show Non-API Modules in
the dialog box to see them in the Module list.
Right-click each of C/C++ modules, choose Edit, and select
Implementation Version.
Creating a Test Application
Choose File > New Project. Select the Samples > C/C++
> C/C++ category, and the Args project. Click Next.
On the Project Name and Location page, set the Project
Location to an appropriate folder on your disk. Click Finish.
The Args_1 project is created. Open the
arg.c source file in the editor. We will use this file to test our plugin.
Now we will use the NetBeans API to add highlighting functionality to
the C/C++ editor.
Creating a Highlighting Provider
Right-click the
org.netbeans.modules.markoccurrences package in the
Source Packages node of the Mark Occurrences
project and choose New > Java Class.
Name the new class MarkOccurrencesHighlighter and click
Finish.
Replace the code in the new
class with the following code:
package org.netbeans.modules.markoccurrences;
import java.awt.Color;
import java.lang.ref.WeakReference;
import javax.swing.JEditorPane;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.StyleConstants;
import org.netbeans.api.editor.settings.AttributesUtilities;
import org.netbeans.modules.cnd.modelutil.CsmUtilities;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
import org.openide.cookies.EditorCookie;
import org.openide.loaders.DataObject;
public class MarkOccurrencesHighlighter implements CaretListener {
private static final AttributeSet defaultColors = AttributesUtilities.createImmutable(StyleConstants.Background, new Color(236, 235, 163));
public void caretUpdate(CaretEvent e) {
bag.clear();
bag.addHighlight(0, 5, defaultColors);
}
private final WeakReference<Document> weakDoc;
public MarkOccurrencesHighlighter(Document doc) {
bag = new OffsetsBag(doc);
weakDoc = new WeakReference<Document>((Document) doc);
DataObject dobj = NbEditorUtilities.getDataObject(weakDoc.get());
JEditorPane[] panes = CsmUtilities.getOpenedPanesInEQ(dobj.getCookie(EditorCookie.class));
if (panes != null && panes.length > 0) {
panes[0].addCaretListener(this);
}
}
private final OffsetsBag bag;
public OffsetsBag getHighlightsBag() {
return bag;
}
}
This class does not provide any smart functionality yet. It
only registers a listener to caret events and highlights
the first symbols of the document.
Creating and Registering HighlightsLayerFactory
Now let's let NetBeans know about our new highlight
provider by creating HighlightsLayerFactory.
Add a new Java class to the project sources and name it
MarkOccurrencesHighlightsLayerFactory.
Replace the code in the new class with the following code:
package org.netbeans.modules.markoccurrences;
import javax.swing.text.Document;
import org.netbeans.spi.editor.highlighting.HighlightsLayer;
import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory;
import org.netbeans.spi.editor.highlighting.ZOrder;
public class MarkOccurrencesHighlightsLayerFactory implements HighlightsLayerFactory {
public static MarkOccurrencesHighlighter getMarkOccurrencesHighlighter(Document doc) {
MarkOccurrencesHighlighter highlighter = (MarkOccurrencesHighlighter)doc.getProperty(MarkOccurrencesHighlighter.class);
if (highlighter == null) {
doc.putProperty(MarkOccurrencesHighlighter.class, highlighter = new MarkOccurrencesHighlighter(doc));
}
return highlighter;
}
public HighlightsLayer[] createLayers(Context context) {
return new HighlightsLayer[] {
HighlightsLayer.create(
MarkOccurrencesHighlighter.class.getName(),
ZOrder.CARET_RACK.forPosition(2000),
true,
getMarkOccurrencesHighlighter(context.getDocument()).getHighlightsBag())
};
}
}
We have provided an implementation of
HighlightsLayerFactory that creates just one highlighting layer
with data provided by the MarkOccurrencesHighlighter
class.
Now we need to register this factory in layer.xml.
Open the layer.xml in the
org.netbeans.modules.markoccurrences package and
change its content to the following:
In the caretUpdate() method we use
CsmReferenceResolver to find references to the
language entity under the cursor. If there is a valid entity
there, we ask CsmReferenceRepository about all
occurrences of the same entity in the
file and store their offsets. The getCsmFile() method is
a plumbing code to be sure we don't keep any language model data.
Press Ctrl-Shift-I to fix imports (or right-click and choose Fix Imports).
Build and run the project.
If you put the cursor on the argc parameter of
main(), you will see the highlighting shown below:
Click in the different places in the file to see how mark occurrences is
working. You may want to try more complex projects to see how it works with classes, macros, etc.
Our current code is good enough for static text, but it can
produce severe delays during editing of the file. The delays
happen because we start searching immediately after
each key press. To solve this issue we will delay the task for
analyzing code, and if the cursor position was changed before the
task started, we will cancel it and reschedule.
In the MarkOccurrencesHighlighter.java class, change
the previous caretUpdate() implementation to the
following code:
public void caretUpdate(CaretEvent e) {
bag.clear();
lastCaret = e.getDot();
scheduleUpdate();
}
private int lastCaret;
private RequestProcessor.Task task = null;
private final static int DELAY = 1000;
public void scheduleUpdate() {
if (task==null) {
task = RequestProcessor.getDefault().create(new Runnable() {
public void run() {
CsmFile file = getCsmFile();
if (file != null) {
CsmReference ref = CsmReferenceResolver.getDefault().findReference(file, lastCaret);
if (ref!=null && ref.getReferencedObject()!=null) {
Collection<CsmReference> out = CsmReferenceRepository.getDefault().getReferences(ref.getReferencedObject(), file, true);
for (CsmReference csmReference : out) {
bag.addHighlight(csmReference.getStartOffset(), csmReference.getEndOffset(), defaultColors);
}
}
}
}
}, true);
task.setPriority(Thread.MIN_PRIORITY);
}
task.cancel();
task.schedule(DELAY);
}
In this code block we use
org.openide.util.RequestProcessor to handle our code
analyzing task. If we get several caret updates we will just cancel
the previous task,
remember the cursor position, and reschedule the task for a
later time.
Fix imports, then build and run the project.
Now you won't notice any delays when typing a large chunk of code.