How Does "Mark Occurrences" Work?

Yesterday's code generator was made possible by Editor Library 2: "The NetBeans editor infrastructure provides an implementation of the Swing Text package APIs as well as some additional features such as syntax coloring, code folding, braces matching, etc."

The Code Generator SPI is in that module, but so is the Highlighting SPI. There's several usecases that relate to this SPI, as you can read in that link. Yesterday I came across a very cool tutorial that describes one of them: Extending the C/C++ Editor in NetBeans IDE 6.0 to Provide Mark Occurrences Highlighting. That's an excellent tutorial by Sergey Grinev from the NetBeans C++ team in St. Petersburg. Let's get to know this SPI while doing something pretty useful: we'll create a mark occurrences plugin for HTML files on the NetBeans Platform (i.e., typically, in NetBeans IDE, but also applicable to any other HTML file open in an application on the NetBeans Platform):

Above, I selected one instance of "you" and then all matching instances were highlighted. Try it. Doesn't work, does it? That's because this functionality is not part of the IDE, but part of my plugin. If you're interested in this functionality as a user, get it here from the Plugin Portal:

http://plugins.netbeans.org/PluginPortal/faces/PluginDetailPage.jsp?pluginid=9441

To create the above, I simply copied Sergey's code, except that I read the content of HTML files rather than C++ (or C) files and then changed the layer.xml accordingly. My MarkOccurrencesHighlightsFactory is identical to Sergey's:

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;
    }

    @Override
    public HighlightsLayer[] createLayers(Context context) {
        return new HighlightsLayer[]{
                    HighlightsLayer.create(
                    MarkOccurrencesHighlighter.class.getName(),
                    ZOrder.CARET_RACK.forPosition(2000),
                    true,
                    getMarkOccurrencesHighlighter(context.getDocument()).getHighlightsBag())
                };
    }

}

Here's the layer.xml registration:

<folder name="Editors">
    <folder name="text">
        <folder name="html">
            <file name="org-netbeans-modules-markoccurrences-MarkOccurrencesHighlightsLayerFactory.instance"/>
        </folder>
    </folder>
</folder>

And, finally, the CaretListener, which picks up whatever is selected and then finds matching occurrences:

package org.netbeans.modules.markoccurrences;

import java.awt.Color;
import java.lang.ref.WeakReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.JTextComponent;
import javax.swing.text.StyleConstants;
import org.netbeans.api.editor.settings.AttributesUtilities;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
import org.openide.cookies.EditorCookie;
import org.openide.loaders.DataObject;
import org.openide.util.RequestProcessor;

public class MarkOccurrencesHighlighter implements CaretListener {

    private static final AttributeSet defaultColors =
            AttributesUtilities.createImmutable(StyleConstants.Background,
            new Color(236, 235, 163));
    private final OffsetsBag bag;
    private JTextComponent comp;
    private final WeakReference weakDoc;

    public MarkOccurrencesHighlighter(Document doc) {
        bag = new OffsetsBag(doc);
        weakDoc = new WeakReference((Document) doc);
        DataObject dobj = NbEditorUtilities.getDataObject(weakDoc.get());
        EditorCookie pane = dobj.getCookie(EditorCookie.class);
        JEditorPane[] panes = pane.getOpenedPanes();
        if (panes != null && panes.length > 0) {
            comp = panes[0];
            comp.addCaretListener(this);
        }
    }

    @Override
    public void caretUpdate(CaretEvent e) {
        bag.clear();
        scheduleUpdate();
    }
    private RequestProcessor.Task task = null;
    private final static int DELAY = 100;

    public void scheduleUpdate() {
        if (task == null) {
            task = RequestProcessor.getDefault().create(new Runnable() {

                public void run() {
                    String selection = comp.getSelectedText();
                    if (selection != null) {
                        Pattern p = Pattern.compile(selection);
                        Matcher m = p.matcher(comp.getText());
                        while (m.find() == true) {
                            int startOffset = m.start();
                            int endOffset = m.end();
                            bag.addHighlight(startOffset, endOffset, defaultColors);
                        }
                    }
                }
            }, true);
            task.setPriority(Thread.MIN_PRIORITY);
        }
        task.cancel();
        task.schedule(DELAY);
    }

    public OffsetsBag getHighlightsBag() {
        return bag;
    }
}

The only difference from Sergey's tutorial is the bit in bold above. Nothing magical, not using any NetBeans APIs, except that whenever a matching pattern is found, it is added to the "bag" (hello, lookup, I knew you well), and highlighted. The only difference between my implementation of "mark occurrences" and the typical implementation is that in my case one actually needs to select something, rather than simply putting the caret in the middle of some word. But that's fine, I think this does the job pretty well. Not only is this a nice introduction to this SPI, but now there is something I've been missing for a while—mark occurrences for HTML files. I've written to Vladimir Voskresensky to ask him what he did to make navigation between marked occurrences possible (as described here) because that would be very cool to add to this plugin. It works pretty well together with Ctrl-F (i.e., search), so that you can search for a word while using mark occurrences for another word (so, you have a double-edged search, as in Java files), as you can see here (orange is from Ctrl-F, while yellow is from mark occurrences):

I'm looking forward to exploring the Highlighting SPI in other contexts too, now that I have this example as an entry point.

Comments:

Cool. I am curious Geertjan, would this be the API to use, if one wanted to implement a feature which could filter away the noise of non-essential code.

Say I wanted to be able to toggle a button up in the editor toolbar, which would cause all System.out.print/Logger statements as well as catch blocks to be rendered almost invisible (until my caret is on that line).

I often find myself using too few logging statements and uniting exception handling into one catch, just to avoid this "noise" which obscures what my code is actually trying to do.

Posted by Casper on May 18, 2008 at 12:14 AM PDT #

Casper, definitely. This API is specifically for rendering text in editors. So your usecase makes perfect sense. When will you make it? :-)

Posted by Geertjan on May 18, 2008 at 02:45 AM PDT #

Oh great, thanks for the info. I think will look into it this week if off-work time (and girlfriend) permits. ;)

Posted by Casper on May 18, 2008 at 11:17 AM PDT #

cloo

Posted by Billie-Jo Martin-Conlon on May 26, 2008 at 02:57 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« July 2014
SunMonTueWedThuFriSat
  
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  
       
Today