GSF Registration

WARNING: Registration is one of the rough spots in GSF which will need to be changed. This section describes the current way to do it, which is definitely not a great solution.

GSF is based on mime types. You register mime services for a particular mime type, such as text/x-ruby or text/javascript. When you are adding a new language, you should go and see if there is a common mime type that is associated with your language, and if so use it. It doesn't really matter what you choose, since GSF doesn't interpret it in any way, but by picking something standard, other modules have a chance of using your editor services. For example, in the property sheet code, used by the Visual Web Pack, some properties are associated with JavaScript onclick handlers. This code just creates a JEditorPane and sets the mime type to text/javascript. "Magically" this makes syntax highlighting etc. in these customizers for JavaScript attributes work, since GSF has registered editing services for this mimetype.

The first think you'll want to do is make sure there is a mime resolver for files of the type you're trying to edit. This is described in detail in the Mime Resolver document. There is nothing GSF specific about this, but if you're trying to start writing a language support, you'll want to begin there. Registering a mime resolver will tell the IDE how to figure out the mime type of a file, but it doesn't create NetBeans DataLoaders or DataObjects for these files. GSF does that, as soon as you register the mime type with GSF.

Layer Registration

To register your mime type with GSF, you'll want to modify your layer as follows:

    
    <folder name="GsfPlugins">
        <folder name="text">
            <folder name="javascript">
                <file name="language.instance">
                    <attr name="instanceClass" stringvalue="org.netbeans.modules.javascript.editing.JsLanguage"/>
                </file>
            </folder>
        </folder>
    </folder>
            
This tells GSF that it should "own" the mime type "text/javascript". (NOTE: When you're describing mime types in the layer system, you must use nested folders for the mimetype. You can NOT create a name="text/javascript" item; it must be a folder named text with a folder named javascript inside it!). Once you've done this, GSF will claim files of this mime type and register its own editor kit with it, and so on. (There is a way to use custom loaders with GSF if you for example are trying to mix and match GSF with Schliemann or you have existing code you are trying to integrate; that will be described later in this document.)

Build Modification

In addition, you also need to add the following target to your project's build file, such that at build time, GSF gets a chance to interject some things into your layer file. In earlier version, GSF would do this at runtime, by modifying your user directory such that it would merge content into the layer file, but this has had several disadvantages. An extra build step avoids this problem. It does however mean that when GSF changes incompatibly, it cannot easily account for older module registrations. Until this issue is resolved, you may have to regenerate your module when versions change.

            <target name="jar" depends="init,compile,jar-prep" unless="is.jar.uptodate">
                <taskdef name="gsfjar" classname="org.netbeans.modules.gsf.GsfJar" classpath="${nb_all}/gsf.api/anttask/gsfanttask.jar:${nb_all}/nbbuild/nbantext.jar"/>
                <gsfjar jarfile="${cluster}/${module.jar}" compress="${build.package.compress}" index="${build.package.index}" manifest="${manifest.mf}" stamp="${cluster}/.lastModified">
                    <fileset dir="${build.classes.dir}"/>
                </gsfjar>
            </target>
        

The Language Configuration

In the above registration, we have named the JsLanguage class as the language configuration object for this class. This JsLanguage object is just an instance of the GsfLanguage interface. It's actually a subclass of the DefaultLanguageConfig class in the GSF SPI package, which implements this interface.

The GSF language object defines key characteristics about your language. The most important method you have to implement is

    
    @NonNull Language getLexerLanguage();
            
This is referring to the Lexer API's Language class. GSF is built on top of the Lexer API. You must create a Lexer for your language. The Lexer API is already quite well documented so I won't repeat it here (though I should include a documentation reference link).

The first step is implementing a Lexer for your language, and returning its language from your language configuration subclass. If you subclass DefaultLanguageConfig, you are finally ready to just test things. You can now start the IDE, double on source files in the file navigator, which should open them in the editor - and they should be properly syntax highlighted! There is more information about the lexer aspects in the lexing document.

Lexer Registration and Colors

You also need to register the lexer language with NetBeans. This is described in the lexing document in more detail.

Other Language Configuration settings

There are some other methods in the language configuration class you can override.
MethodPurpose
String getLineCommentPrefix()
If your language supports line comments, return the comment string here. For example, for Java and JavaScript it's //, and for Ruby it's #. If you return non null from this method, GSF will enable the comment, uncomment and toggle comment actions, add them to your toolbar and implement an editor action which commments, uncomments and toggles comments in your source files.
boolean isIdentifierChar(char c)
This method lets you return whether a character is an identifier character in your language. This method will be used to for example automatically handle the case where the user double clicks in your editor; this should select a complete identifiers. By default in NetBeans you get the Java behavior, but in a language like JavaScript for example, $ should also be included in the double click selection.

(Unfortunately, the current design doesn't make it easy to handle more complicated scenarios like Ruby, where a $ should only be accepted as a prefix of the identifier, and a ! should only be accepted as a suffix. I plan to refine this approach a bit to handle this.

If you extend the DefaultLanguageConfig class, there are additional methods you can subclass. I'll describe these after I introduce the other services you can register with NetBeans.

Parsing

Most features in GSF will require parse information. This relies upon calling your own implementation of the Parser interface. If you are subclassing the DefaultLanguageConfig class, just override the getParser() method:

    
    @Override
    public Parser getParser() {
        return new JsParser();
    }
            
Alternatively, you can register this service directly in the layer:
    
    <folder name="GsfPlugins">
        <folder name="text">
            <folder name="javascript">
                ...
                <file name="parser.instance">
                    <attr name="instanceClass" stringvalue="org.netbeans.modules.javascript.editing.JsParser"/>
                </file>
            </folder>
        </folder>
    </folder>
            

Indexing

This is just like the parser registration. You need to implement the Indexer interface, and then register it, either via subclassing DefaultLanguageConfig and overriding getIndexer():

    
    @Override
    public Parser getIndexer() {
        return new JsIndexer();
    }
            
Alternatively, you can register this service directly in the layer:
    
    <folder name="GsfPlugins">
        <folder name="text">
            <folder name="javascript">
                ...
                <file name="indexer.instance">
                    <attr name="instanceClass" stringvalue="org.netbeans.modules.javascript.editing.JsIndexer"/>
                </file>
            </folder>
        </folder>
    </folder>
            

Other Services

You get the idea. You can override configuration functiosn in DefaultLanguageConfig, or register services directly in the layer. Sometimes you can do a combination of things. For example, in the Ruby plugin, most editing related services are implemented by and registered in the Ruby Editing module. These are instantiated directly by the RubyLanguage configuration class. However, Ruby Quickfixes are implemented in a separate module, and therefore the HintsProvider implementation for Ruby is registered via the layer in the hint module.

Exceptions!

HACK ALERT: There are two ugly exceptions to the ability to register services with the DefaultLanguageConfig. Two attributes must be initialized via the layer instead:

  1. Structure scanners
  2. The customEditorKit attribute
Structure scanners are used to for example populate the navigator view. The customEditorKit attribute will be described later in this document. The problem with both of these attributes is that they are needed early during startup, even during IDE sessions where file types of the given mimetype is never loaded! There are implementation reasons for this. Currently, the way we hook into the navigator API, we have to know when we first see the GSF language registrations whether a given language supports navigation (in which case we'll modify the system file system to register a GSF navigator view for this file type). Similarly, we need to know during GSF data loader initialization whether GSF should be on the lookout for files of the given mime type; this is only the case if customEditorKit is false. Well, if GSF were to look for these attributes in the langauge configuration objects, it would have to load and instantiate all the language configuration objects at startup! That would mean initializing a WHOLE bunch of classes at startup, since language configuration classes typically reference a bunch of other services. (I in fact DID try this, and ended up failing the blacklisted class commit validation test, so I had to revert initialization of these two attributes to being layer-only).

Using Custom Loaders and Kits

Normally when you register a mime type with GSF, GSF will automatically handle file type handling for you, by creating NetBeans implementations like DataObjects, creating an EditorKit for these DataObjects, and so on.

However, there are cases where you have an existing EditorKit or DataLoader, and you want to add GSF support selectively. To do that, you need to tell GSF to back off. You do that with the useCustomEditorKit attribute, which you add as an attribute to the mime folder in the GsfPlugins folder in the layer:

    
    <folder name="GsfPlugins">
        <folder name="application">
            <folder name="x-httpd-eruby">
                <attr name="useCustomEditorKit" boolvalue="true"/>
                ...
            </folder>
        </folder>
    </folder>
            
When GSF sees this, it will not add ANY editing services for you. You are now free to add these in yourself in your layer. This is done by the HTML module for example, which had a lot of existing legacy code we didn't want to rewrite (at least not yet). HTML already has DataObjects (which were subclassed in other modules), it had custom code to handle formatting and code completion, and we wanted to keep these classes using the old code for now. So, the HTML module specifies useCustomEditorKit.

With the useCustomEditorKit attribute you can use your own EditorKit implementation. There is a price to pay though. When you do this, you need to manually register all the GSF implementations you do want to use. For example, if you're providing a semantic highlighter, you have to register the GSF highlighting factory in the Editors folder:

    
    <folder name="Editors">
        <folder name="text">
            <folder name="html">
                Required for semantic highlighting:
                <file name="org-netbeans-modules-gsfret-editor-semantic-HighlightsLayerFactoryImpl.instance"/>
            
                Required for code folding:
                <folder name="FoldManager">
                    <file name="org-netbeans-modules-gsfret-editor-fold-GsfFoldManagerFactory.instance"/>
                </folder>
                <folder name="SideBar">
                    <file name="org-netbeans-modules-editor-gsfret-GsfCodeFoldingSideBarFactory.instance">
                        <attr name="position" intvalue="1200"/>
                    </file>
                </folder>
                Required for GSF go to declaration:
                <folder name="HyperlinkProviders">
                    <file name="GsfHyperlinkProvider.instance">
                        <attr name="instanceClass" stringvalue="org.netbeans.modules.gsfret.editor.hyperlink.GsfHyperlinkProvider"/>
                        <attr name="instanceOf" stringvalue="org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt"/>
                    </file>
                </folder>
                Required for error status:
                <folder name="UpToDateStatusProvider">
                    <file name="org-netbeans-modules-gsfret-hints-GsfUpToDateStateProviderFactory.instance"/>
                </folder>
                Required for code completion:
                <folder name="CompletionProviders">
                    <file name="org-netbeans-modules-gsfret-editor-completion-GsfCompletionProvider.instance"/>
                </folder>
            </folder>
        </folder>
    </folder>
            
I may be missing some services here - like live code templates and mark occurrences. If you do go this route, you should look at the LanguageRegistry class in the GSF implementation to make sure you're picking up everything you intend to, to make sure you have the right class names, with all the right attributes, etc.
I need to do something to make this more solid. Perhaps I can use .shadow or other forms of aliasing such that all you need to do is reference a service by hand and the correct class name etc. is used. I think I've seen this for actions (which are registered in one place, and just a shadow used elsewhere to link to it) so it might work in this case if I switch to a logical name and use the instanceClass attribute instead of the filename to designate the class to be instantiated.

Mixing and Matching APIs

You are free to use the NetBeans APIs directly. For example, in addition to relying on GSF's code completion provider, you can add an additional direct implementation of NetBeans' code completion API if you should want to. You don't need to switch to a custom editor kit to do this.

Adding Actions

In NetBeans 6.5, there is a new API which lets you register editor actions (along with keybindings) for arbitrary mime types. This means that you can extend the available actions for an editor type from your modules. GSF used to have an API to help with this, but it's no longer necessary. To do this, you just have to implement the editor BaseAction class, and register it in the Editors mime folder:

    
    <folder name="Editors">
        <folder name="text">
            <folder name="x-ruby">
                ...
                <folder name="Actions">
                    <file name="org-netbeans-modules-ruby-ReflowParagraphAction.instance"/>
                </folder>
            </folder>
        </folder>
    </folder>
            
From here, you can proceed to also create a Popup folder to add it to the context menu for this file type, and/or register a keybinding for it via an XML file referenced from the Keybindings folder.


Tor Norbye <tor@netbeans.org>