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.
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.)
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>
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.
You also need to register the lexer language with NetBeans. This is described in the lexing document in more detail.
There are some other methods in the language configuration class you can override.
Method | Purpose |
---|---|
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.
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>
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>
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.
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:
customEditorKit
attributecustomEditorKit
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).
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.
instanceClass
attribute instead of the filename to designate the class to be instantiated.
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.
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.