How to Create an Editable Diff Viewer

Well, I've learned a lot of cool things about the NetBeans APIs over the years, but this probably takes the cake. All you need to do if you want an editable diff viewer is put a FileObject into the Lookup of a Node and then you can reuse the standard Diff Action that's part of NetBeans IDE.

In the right pane, you can type, i.e., the right pane is editable. All the editing functionality, e.g., code completion, is available too. That's exactly how it is when using NetBeans IDE's diff functionality. No surprise there, since the Diff Action in the IDE is being reused, completely as-is, unchanged, in this scenario.

Here's the definition of the Node above:

public class CustomerNode extends BeanNode<Customer> {
    
    public CustomerNode(Customer bean) throws IntrospectionException {
       this(bean, new InstanceContent());
    }
    
    private CustomerNode(Customer bean, InstanceContent ic) 
            throws IntrospectionException {
        super(bean, Children.LEAF, new AbstractLookup(ic));
        ic.add(bean.getFileObject());
        setDisplayName(bean.getFileObject().getName());
    }

    @Override
    public Action[] getActions(boolean context) {
        return new Action[]{FileUtil.getConfigObject(
                "Actions/Tools/org-netbeans-modules-diff-DiffAction.instance", 
                Action.class)};
    }

} 

What's magical about the above is that the org-netbeans-modules-diff-DiffAction becomes active when one or more Nodes are selected and opens the Diff Viewer for the FileObjects found in the Nodes. So, therefore, since my Customer object is defined by a FileObject read from the layer file, when multiple Customers are selected multiple FileObjects are available (because I put the FileObject into the Node Lookup).

Now, what happens if MORE than two FileObjects are available, i.e., more than two Nodes are selected (or the two selected Nodes have more than two FileObjects in their Lookups)? Well, automatically, the Diff Action is disabled:

And what about if there are LESS than two FileObjects? Then you see the following, i.e., you are prompted to browse on disk for a file that you'd like to compare with the available FileObject:

Next, just make sure that the editor modules for the language of your choice are in the application. For example, because I have the HTML Editor modules, the diff view uses the HTML Editor to display the two FileObjects... because the two FileObjects are recognized as being HTML files, since their file extension is "html".

And what if your Customer object does not define a FileObject, i.e., you want to compare something different? Somehow, create a FileObject anyway. Put into that FileObject the data you'd like to diff. For example, I have this in the module's layer file:

    <folder name="customers">
        <file name="james.html" url="resources/james.html"/> 
        <file name="john.html" url="resources/john.html"/> 
        <file name="henry.html" url="resources/henry.html"/> 
    </folder> 

I then construct the Node hierarchy from the layer folder you see above:

public class CustomerChildFactory extends ChildFactory<Customer> {
    @Override
    protected boolean createKeys(List<Customer> list) {
        FileObject customersFolder = FileUtil.getConfigFile("customers");
        for (FileObject customerFile : customersFolder.getChildren()) {
            list.add(new Customer(customerFile));
        }
        return true;
    }
    @Override
    protected Node createNodeForKey(Customer key) {
        CustomerNode node = null;
        try {
            node = new CustomerNode(key);
        } catch (IntrospectionException ex) {
            Exceptions.printStackTrace(ex);
        }
        return node;
    }
    
} 

Each file you see in the layer folder snippet above is a FileObject, defined by a name (ending in "html", so that the HTML Editor will be used to display the file) and an HTML file that is within the module. Once I have the Node hierarchy, I put the FileObject into the Node Lookup, as shown in the CustomerNode definition above, after which I reuse the Diff Action, as you can see in the getActions method of the CustomerNode. That's all it takes.

Comments:

That is a nice shortcut indeed!
But what I really like about NetBeans are the possibilities to do stuff manually as well. In this case consider a FileObject fo:

String contentType = fo.getMIMEType();
DiffView view = Diff.getDefault().createDiff(
StreamSource.createSource("Before", "Before", contentType,
new StringReader(beforeText)),
StreamSource.createSource("After", "After", contentType,
new StringReader(afterText)));
Component diffViewComponent = view.getComponent();

And there you have a nice diff component. If used from within the NetBeans
infrastructure with the respective handlers for the file's mime type installed you automatically have syntax highlighted previews of your changes.

Posted by guest on January 09, 2013 at 04:54 AM PST #

When I create a CustomerNode and copy your code in NB 7.2, getActions does not override or implement a method from a supertype.

Posted by Kevin on January 09, 2013 at 08:04 AM PST #

Because you're not extending BeanNode. Since you don't know how Nodes work, please see the "NetBeans Platform Fundamentals" section on the NetBeans Platform Learning Trail.

Posted by Geertjan on January 09, 2013 at 09:11 AM PST #

Actually, it was because util.Mutex.Action was imported instead of swing.Action.

Posted by Kevin on January 09, 2013 at 11:26 AM PST #

So now the code works for you and the Diff Action is working?

Posted by Geertjan on January 09, 2013 at 02:32 PM PST #

Nice, thank you Geertjan.
FYI: I created a FAQ entry at http://wiki.netbeans.org/DevFaqEditorHowToAddDiffView

Posted by markiewb on January 09, 2013 at 11:29 PM PST #

Not quite. When I put the Customers folder under / in layer.xml, it never appeared when I ran the app. So I put it under Menu/Tools. Created the html files, which are reachable from the Project window Important Files | XML Layer | Menu | Tools | Customers | xxx.html. CustomerChildFactory.createKeys getConfigFile refers to "Menu/Tools/Customers."

But when I run it, a breakpoint at CustomerChildFactory.createKeys is never reached. Tools | Customers shows an empty list.

Posted by Kevin on January 10, 2013 at 05:21 AM PST #

It's just a plain ordinary Node hierarchy, with a FileObject in the Node Lookup. So, if it's not working for you, you really need to follow some basic tutorials about creating Node hierarchies.

Posted by Geertjan on January 10, 2013 at 11:05 AM PST #

I.e., you haven't got a TopComponent defined. Because that's where you call the ChildFactory from. Again, this is common knowledge to anyone who understands how Node hierarchies work.

Posted by Geertjan on January 10, 2013 at 11:06 AM PST #

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
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today