FeaturesPluginsDocs & SupportCommunityPartners

Extending the C/C++ Editor in NetBeans IDE 6.0 to Provide Mark Occurrences Highlighting

Content on this page applies to NetBeans IDE 6.0 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

  1. Choose File > New Project. In the New Project wizard, select NetBeans Modules under Categories and Module under Projects. Click Next.
  2. 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.
  3. On the Basic Module Configuration page, type org.netbeans.modules.markoccurrences in the Code Name Base field. Click Finish.
  4. 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.

    Projects window showing required libraries
  5. Right-click each of C/C++ modules, choose Edit, and select Implementation Version.


Creating a Test Application

  1. Choose File > New Project. Select the Samples > C/C++ > C/C++ category, and the Args project. Click Next.
  2. On the Project Name and Location page, set the Project Location to an appropriate folder on your disk. Click Finish.
  3. The Args_1 project is created. Open the arg.c source file in the editor. We will use this file to test our plugin.

top

Creating Highlighting Infrastructure

Now we will use the NetBeans API to add highlighting functionality to the C/C++ editor.

Creating a Highlighting Provider

  1. Right-click the org.netbeans.modules.markoccurrences package in the Source Packages node of the Mark Occurrences project and choose New > Java Class.
  2. Name the new class MarkOccurrencesHighlighter and click Finish.
  3. 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.

  1. Add a new Java class to the project sources and name it MarkOccurrencesHighlightsLayerFactory.
  2. 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())
            };
        }
          }
  3. 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:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
    <filesystem>
        <folder name="Editors">
            <folder name="text">
                <folder name="x-c++">
                    <file name="org-netbeans-modules-markoccurrences-MarkOccurrencesHighlightsLayerFactory.instance" />
                </folder>
                <folder name="x-c">
                    <file name="org-netbeans-modules-markoccurrences-MarkOccurrencesHighlightsLayerFactory.instance" />
                </folder>
            </folder>
        </folder>
             </filesystem>

Now we are ready for first run of our highlighter.

  1. Build the project.
  2. When the project has successfully built, run it.
  3. Open the Args project we created in the previous section.
  4. Open the args.c file in the editor and click anywhere in the file. The highlighting will look like the following example:

    Example of the
           highlighting

Great. Our highlighter works. Now let's teach it to be more useful.

top

Gathering Information from the C/C++ Language Model

  1. In the MarkOccurrencesHighlighter.java class, remove our draft caretUpdate() implementation and add the following code:
        private WeakReference<CsmFile> weakFile;
        public void caretUpdate(CaretEvent e) {
            bag.clear();
            CsmFile file = getCsmFile();
            if (file != null) {
                CsmReference ref = CsmReferenceResolver.getDefault().findReference(file, e.getDot());
                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);
                    }
                }
            }
        }
        private CsmFile getCsmFile() {
            if (weakFile == null || weakFile.get() == null) {
                if (weakDoc == null || weakDoc.get() == null) {
                    return null;
                }
                DataObject dobj = NbEditorUtilities.getDataObject(weakDoc.get());
                CsmFile file = CsmUtilities.getCsmFile(dobj, false);
                if (file != null) {
                    weakFile = new WeakReference<CsmFile>(file);
                } else {
                    return null;
                }
            }
            return weakFile.get();
          }
    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.
  2. Press Ctrl-Shift-I to fix imports (or right-click and choose Fix Imports).
  3. Build and run the project.
  4. If you put the cursor on the argc parameter of main(), you will see the highlighting shown below:

    Example of the code with marked occurrences
  5. 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.

top

Improving Performance

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.

  1. 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.
  2. Fix imports, then build and run the project.
  3. Now you won't notice any delays when typing a large chunk of code.

top

Downloads

Bookmark this page

del.icio.us furl simpy slashdot technorati digg
Companion
Projects:
MySQL Database Server   Open JDK: an Open SourceJDK   GlassFish Community: an Open Source Application Server    Mobile & Embedded Community    Open Solaris   java.net - The Source for Java Technology Collaboration   Open ESB - The Open Enterprise Service Bus Powered by