Code fragment editing in NetBeans RCP

So, you’ve got a String property that’s a fragment of Java code, and you want to open and edit it within the NetBeans editor; you want access to code completion, hints, and all that NetBeans goodness; and, you want your String property to update every time you hit Save. Well, that was the challenge facing me in integrating the NetBeans editor into Praxis LIVE for version 2.

core:math:add component with code open for editing.

core:math:add component with code open for editing.

Now, Praxis LIVE has always had some support for live-coding components on-the-fly, previously making use of Janino’s built-in compiler and RSyntaxTextArea for code editing. However, with version 2 switching to using javac (though still through Janino), and the new code API allowing for forking core components as well as creating custom ones, it was time to switch to a more powerful editor with code completion, error hints, etc.

The above image shows the built-in core:math:add component (perhaps the simplest) with its code open in the editor. Note that the code you see is a class body – the Janino API allows for compiling class bodies, method bodies and expressions; and loading them into the running JVM.  Note that we’ve got full code completion, including for our custom types.  And while we’re at it, note that lovely new annotation driven API and its relation to the visual ports and controls of the component, all re-definable on-the-fly when you hit Save … but, I digress. 🙂

So, how?

Well, I’m going to explain the various classes that are in use to achieve this, but I’m not going to include the code itself as this post will get even longer, and it’s going to be long enough!  Instead, I’ll link directly off to the classes in the GitHub repository.  That way you’ll also know it’s the latest version of the code.

PXJDataObject

OK, in some ways you don’t need this class (huh, what?!) – what you need is a class that does some of what it does.  This class could be the action that opens your String in the editor.  In the case of Praxis LIVE, our String is contained in a Node.Property.  That property creates a file of the required MIME type in a memory file system, writes the String to it, opens the file for editing and listens for changes.  It is a level of indirection that you might not need.

The important thing to look at is what happens when this file is opened.  Instead of opening itself in the editor, it create a memory file system (yes, another one!), and writes out a proxy Java file. This process needs to know the context of your code String – the code that should surround it to make it into a complete Java class.  In this case, the information is passed in through a FileObject attribute, and includes default imports and superclass.

During the construction of our Java proxy, we insert 3 fixed Strings into the file – default imports, class definition and class ending.  These all make use of the NetBeans’ editor ability for code folds and guarded (uneditable) sections – eg. the class definition from the above image is written out as –

//<editor-fold defaultstate="collapsed" desc="Class Declaration">//GEN-BEGIN:classdec                  
class add1_code extends net.neilcsmith.praxis.core.code.CoreCodeDelegate {
//</editor-fold>//GEN-END:classdec

Finally, we add a file listener to the proxy file, add the PXJDataObject as a file attribute, and open it in the editor.  Our file listener registers every save action and writes back the code as necessary, remembering to remove our 3 fixed Strings as we do so!

NB. You may notice that the class ending brace is not wrapped in an editor fold – unfortunately, doing this will break a variety of refactoring operations that try to insert code immediately before the closing brace – a fun one to debug!

PXJavaEditorSupport

The default Java editor support does not take any notice of guarded sections.  We don’t want our users to be able to edit the surrounding code, and in fact that would break the code we’re using to remove these sections later.  Therefore, we have to provide a custom editor support that loads and saves the file using NetBeans’ GuardedSections API.

The only other notable thing that this class does is call through to the PXJDataObject to get it to dispose of the in-memory proxy file when all editors are closed.

PXJavaDataLoader / PXJavaDataObject

Here we register a new data loader for Java files. This is necessary in order to install our alternative editor support.  The data loader looks for the PXJDataObject we put in the file attributes of the in-memory Java proxy, so that we don’t take over editing for all Java files.

Is that all?

Well, that’s most of the way to achieve in-memory editing, however our code completion won’t work correctly yet.  For that we need two more files.

ClassPathImpl

Firstly, we need to register a ClassPathProvider implementation. Here we check again for our PXJDataObject to make sure we only provide a class path for the right files. We provide a simple source class path based on the parent of the file, which is the root directory of our memory file system. For the compile class path we defer to …

ClassPathRegistry

Here you need to build a class path out of the JARs your code should link to. If you’re editing code for live compilation into your application, as Praxis LIVE is doing, then that will be some or all of the JARs on your application class path. In this class we build up a list of all the JAR files in the modules directory of our core cluster, along with any in the ext sub-directory. We use ClassPathSupport to create a class path from this list, and make sure to also register the class path in the GlobalPathRegistry.

And then?

Well, the next thing Praxis LIVE does is compile the code on each save, analyse those lovely annotations, and inject the compiled code into your running project.  But that’s for another post.

What it should do in your application, well that’s up to you, but hopefully this a useful starter. 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s