X

Geertjan's Blog

  • August 23, 2007

How to Write a Groovy Editor (Part 1)

Geertjan Wielenga
Product Manager
I thought I'd put down on 'paper' the things I've been looking at in relation to Groovy and providing an editor for it. By 'editor' I mean primarily 'syntax coloring', 'code folding', 'code completion', and 'navigator support'. Secondarily I mean 'run' functionality and 'console integration'. The key to everything in the primary section in relation to NetBeans is Schliemann. Fortunately, a Schliemann file for Java already exists (here), so since Groovy is closely related to Java, kind of like its scruffy nephew, let's not reinvent the wheel. Let's just take the Java NBS file, adapt it to our purposes, and add whatever functionality we'd like to provide. That should work, all things being equal (though they seldom are or seem to be), one would think.

  1. Use the Module wizard to create a module project called 'MyGroovyEditor'. Make the code name base 'org.netbeans.modules.mygroovyeditor'.

  2. Right-click the project node, choose New and then Other, and then choose 'Language Support' from the Module Development category in the New Project wizard. Click Next. Type 'Groovy' in File Name. Click Next. Type 'text/x-groovy' in Mime Type and 'groovy' in Extensions. Click Finish.

    The cheerful image that now greets your gaze is as follows:

    Note: The Groovy.nbs file that you see above is a template for a Schliemann file. It already contains a few declarations, but we will end up replacing all of them. However, it is useful in giving you an immediate feeling for what Schliemann is and does.

  3. Next, let's create some templates for Groovy, so that we have a few use cases of the language for trying out our language support features. After a few seconds of googling, I've come up with the following template content:

    class Person {

    firstName

    lastName

    address1

    city

    province

    postcode
    }
    p = new Person(firstName:'Ian', lastName:'Darwin', province:'Ontario')
    println "Hello ${p.firstName} ${p.lastName}"

    The above bit comes from here, while Wikipedia gave me this:

    import groovy.xml.MarkupBuilder
    def myXMLDoc = new MarkupBuilder()
    myXMLDoc.workbook {
    worksheet(caption:"Employees") {
    row(fname:"John", lname:"McDoe")
    row(fname:"Nancy", lname:"Davolio")
    }
    worksheet(caption:"Products") {
    row(name:"Veeblefeetzer", id:"sku34510")
    row(name:"Prune Unit Zappa", id:"sku3a550")
    }
    }
    println myXMLDoc

    ..while elsewhere I find this:

    import java.awt.BorderLayout
    swing = new groovy.swing.SwingBuilder()
    myFrame = swing.frame(title: 'Sample Frame', location:[100,100], size:[300,200]) {
    menuBar {
    menu(text: 'File') {
    menuItem(text: 'Exit', actionPerformed:{System.exit(0)})
    } }
    panel(layout: new BorderLayout()) {
    label(text: 'Input:', constraints: BorderLayout.WEST,
    toolTipText: 'Tool Tip')
    textField(constraints: BorderLayout.CENTER)
    button(text: 'Hello', constraints: BorderLayout.SOUTH,
    actionPerformed:{println("World")})
    }
    }
    myFrame.visible = true

    Let's take these as our three templates. First, before even creating them, let's register them in the layer file:

    <folder name="Templates">
    <folder name="Groovy">
    <file name="Groovy1.groovy" url="Groovy1.groovy">
    <attr name="template" boolvalue="true"/>
    </file>
    <file name="Groovy2.groovy" url="Groovy2.groovy">
    <attr name="template" boolvalue="true"/>
    </file>
    <file name="Groovy3.groovy" url="Groovy3.groovy">
    <attr name="template" boolvalue="true"/>
    </file>
    </folder>
    </folder>

    And now use the Empty File template in the Other category, in the New File wizard, to create 'Groovy1.groovy', 'Groovy2.groovy', and 'Groovy3.groovy'. Put them in the main package, where the other files are already. Paste the first snippet above into the first file, the next into the second, and the third in the last.

  4. Our next step can be summed up by the word 'plagiarism'. Click here and copy the entire content into your 'Groovy.nbs' file. When I did this, I found several red error marks in the right side of the editor. I found that it wasn't necessary to fix everything but, at the time of writing, the content I got had some flaws in the Schliemann definitions, in the COLOR definitions which, at the time of this writing, begins in line 458. There, for each COLOR definition, add a colon after each COLOR name, so that there is a colon right before and right after each name, such as here for 'separator':

    COLOR:separator: {
    color_name: "operator";
    }

    Do this for each COLOR definition. Then also for the Navigator definitions. Then delete the ERROR definitions; let's not worry about those yet, they're just going to get in our way later. The remaining error markings are harmless. Fix the BUNDLE declaration to the following:

    BUNDLE "org.netbeans.modules.mygroovyeditor.Bundle"

  5. Now add 'def' to the long list of keyword TOKENs, as shown below:

    For now, we'll just stick the 'def' keyword in with the standard Java keywords, but later we'll take this handy PDF containing Groovy keywords and give the Groovy-specific keywords special treatment.

  6. Right, looks like we're good to go. Right-click the project node and choose 'Install/Reload in Target Platform'. Our editor is built, a new instance of the IDE starts up, and the editor is installed within it. Once the IDE is started, create a new Java application project (or, in fact, any project at all). For the project you created, open the New File wizard and see that you have three templates in a new Groovy folder:

    Also notice the icon that accompanies these three files. That's an icon that all Schliemannized file types get by default. So, from the fact that our templates have this icon, we can ascertain that the 'text/x-groovy' MIME type has been successfully registered and that it has been mapped to the 'groovy' file extension. Let's take the second template and then click through the remainder of the wizard. Once the wizard is complete, you should see the following in the editor:

    Take special note of the fact that 'def' is blue, above. We've simply assigned it to the 'keyword' TOKEN, hence it is treated like any other keyword.

    Note: The TOKEN named 'keyword' and the TOKEN named 'string' each have a default color, as you can see above. That's true for all NBS files: if your NBS file has a TOKEN called 'keyword', then anything assigned to that TOKEN has the default color blue, as shown above, so that (unless you want a different color) you don't need to specifically define a color for 'keyword'. Same situation for 'string', except that here the default color is brown, as can be seen above.

  7. Let's now have a look at the grammar section of the file. To begin with, let's look at the way that the classes are defined and how import statements are handled. Right after the TOKEN definitions, you should (at the time of this writing) find the following declarations:

    S = [PackageDeclaration] (ImportDeclaration)\* (TypeDeclaration)\*;
    PackageDeclaration = "package" Name ";";
    ImportDeclaration = "import" Name ["." "\*"] ";";
    TypeDeclaration = ClassDeclaration | InterfaceDeclaration | ";";
    ClassDeclaration = ClassDeclarationModifiers UnmodifiedClassDeclaration;
    ClassDeclarationModifiers = ("abstract" | "final" | "public")\*;
    UnmodifiedClassDeclaration = "class" <identifier> ["extends" Name] ["implements" NameList] "{" ClassBody "}";
    NestedClassDeclaration = NestedClassDeclarationModifiers UnmodifiedClassDeclaration;
    NestedClassDeclarationModifiers = ("static" | "abstract" | "final" | "public" | "protected" | "private")\*;
    ClassBody = ( Initializer | NestedClassDeclaration | NestedInterfaceDeclaration |
    ConstructorDeclaration | MethodDeclaration | FieldDeclaration )\*;
    InterfaceDeclaration = InterfaceDeclarationModifiers UnmodifiedInterfaceDeclaration;
    InterfaceDeclarationModifiers = ("abstract" | "public")\*;
    NestedInterfaceDeclaration = NestedInterfaceDeclarationModifiers UnmodifiedInterfaceDeclaration;
    NestedInterfaceDeclarationModifiers = ( "static" | "abstract" | "final" | "public" | "protected" | "private" )\*;
    UnmodifiedInterfaceDeclaration = "interface" <identifier> ["extends" NameList] "{" InterfaceMemberDeclaration "}";
    InterfaceMemberDeclaration = ( NestedClassDeclaration | NestedInterfaceDeclaration |
    MethodDeclaration | FieldDeclaration )\*;

    Well, that's our strict ol' uncle Java. Let's Groovify it, by making the modifiers and semi-colons optional. Below, the lines in bold are those that I changed:

    S = [PackageDeclaration] (ImportDeclaration)\* (TypeDeclaration)\*;
    PackageDeclaration = "package" Name [";"];
    ImportDeclaration = "import" Name ["." "\*"] [";"];
    TypeDeclaration = ClassDeclaration | InterfaceDeclaration | [";"];
    ClassDeclaration = [ClassDeclarationModifiers] UnmodifiedClassDeclaration;

    ClassDeclarationModifiers = ("abstract" | "final" | "public")\*;
    UnmodifiedClassDeclaration = "class" <identifier> ["extends" Name] ["implements" NameList] "{" ClassBody "}";
    NestedClassDeclaration = [NestedClassDeclarationModifiers] UnmodifiedClassDeclaration;
    NestedClassDeclarationModifiers = ("static" | "abstract" | "final" | "public" | "protected" | "private")\*;
    ClassBody = ( Initializer | NestedClassDeclaration | NestedInterfaceDeclaration |
    ConstructorDeclaration | MethodDeclaration | FieldDeclaration )\*;
    InterfaceDeclaration = [InterfaceDeclarationModifiers] UnmodifiedInterfaceDeclaration;
    InterfaceDeclarationModifiers = ("abstract" | "public")\*;
    NestedInterfaceDeclaration = [NestedInterfaceDeclarationModifiers] UnmodifiedInterfaceDeclaration;
    NestedInterfaceDeclarationModifiers = ( "static" | "abstract" | "final" | "public" | "protected" | "private" )\*;
    UnmodifiedInterfaceDeclaration = "interface" <identifier> ["extends" NameList] "{" InterfaceMemberDeclaration "}";
    InterfaceMemberDeclaration = ( NestedClassDeclaration | NestedInterfaceDeclaration |
    MethodDeclaration | FieldDeclaration )\*;

    So I used square brackets to make the modifiers optional (since public is assumed) and I also made the semi-colons optional.

  8. Now, let's immediately add code folding functionality, for all 'types', which, based on the above Schliemannesque declarations, are classes and interfaces. So add this line in the 'code folding' section, which is near the end of the file:

    FOLD:TypeDeclaration:"{...}"

    Excellent, let's see where things are and install the editor as before. This time, use the first template and then notice that when you complete the wizard, your class definition has a code fold:

    Hurray! Believe me, creating a code fold in Java, on the NetBeans Platform, is as much fun as a slap in the face. The above... well, it couldn't be any simpler.

    Also notice that there seems to be some kind of error marking for the single-quotes around the strings, in the screenshot above. Seems Groovy is flexible enough to cater for single quotes, while grumpy Java has problems with it. So redefine the TOKEN named 'string' to the following:

    TOKEN:string: (
    ["\\""]|["'"]
    ( [\^ "\\"" "\\n" "\\r"] |
    ("\\\\" ["r" "n" "t" "\\\\" "\\'" "\\""])
    )\*
    ["\\""]|["'"]
    )

    I changed the first line and the last line of the declaration above, so that either a double-quote or a single-quote is optional. Now, when I reinstall the editor, the small error markings under the single-quotes have vanished:

  9. Let's now look at the import statements, so that we can end up with the same result as shown in the previous blog entry. First, we'll color the import statements. From the earlier snippet, we know that the name of the grammar rule defining import statements is 'ImportDeclaration'. Hence, we end up with this COLOR definition, to make our import statements a festive green:

    COLOR:ImportDeclaration: {
    foreground_color: "green";
    font_type: "bold+italic";
    }

    Install the editor again and then open the third template, which should now look as follows:

    However, as in the previous blog entry, we want to be able to add the Groovy keyword 'as', together with an alias, in such a way that the 'as foo' ends up looking different to the rest of the line. That way, the user will be able to read and maintain the import aliases much better, because they'll be easy to identify. Currently, if we use the 'as' keyword, the import statement looks as follows:

    ...which is a bit dull. Let's make it red instead. Find this line, back in the grammar secion, which defines your import statements:

    ImportDeclaration = "import" Name ["." "\*"] [";"];

    Replace the above line with these three:

    ImportDeclaration = ImportBasisDeclaration [ImportWithAliasDeclaration] [";"];
    ImportBasisDeclaration = "import" Name ["." "\*"];
    ImportWithAliasDeclaration = "as" <identifier>;

    And now replace the COLOR definition for the ImportDeclaration rule, with these two COLOR definitions:

    COLOR:ImportBasisDeclaration: {
    foreground_color: "green";
    }
    COLOR:ImportWithAliasDeclaration: {
    foreground_color: "red";
    font_type: "bold+italic";
    }

    Install the editor again and now you should see this result, when you add some alias to your import statements:

  10. Notice something else... when you put the cursor on an open bracket, or a close bracket, its matching bracket is highlighted in green, together with the selected bracket, so that you can see which bracket matches with which other bracket:

    Above, you see one example, and below another:

    That's just default behavior. (I'm sure that, some months ago, when I looked at all this for the first time, the above brace matching had to be explicitly declared. However, makes sense if that isn't necessary anymore, because brace matching such as the above is always going to have this behavior, so if it is taken care of under the hood, then that makes perfect sense to me.) Both curly braces and standard brackets are handled in this way.

  11. Now we'll give all Groovy-specific keywords a distinct color. Let's make it red, so that our import alias, which is also Groovy-specific, matches with our other Groovy-specific syntax. From our Groovy reference card, we can ascertain that the following keywords are distinctly Groovy:
    • as
    • def
    • in
    • property

    Fine. Remove 'def' from the TOKEN called 'keyword'. Create a new TOKEN, called 'keywordGroovy', and add the above keywords, so that you have this result:

    TOKEN:keywordGroovy: (
    "as" |
    "def" |
    "in" |
    "property"
    )

    Then add a new COLOR definition, amongst the other existing COLOR definitions:

    COLOR:keywordGroovy: {
    foreground_color: "red";
    font_type: "bold+italic";
    }

    After reinstalling the editor, you should see things similar to this, when working with import statements, Groovy import aliases, and the Groovy keywords listed above:

  12. In a similar way to how the class and interface declarations were adapted for Groovy, you could now adapt methods, variables, fields, and so on, just changing the definitions from Java-oriented to Groovy-oriented.

  13. Next, let's think about code completion. We'll turn here in the NetBeans sources, to see how Schliemann provides language support for JavaScript. There are quite a few things to learn in that directory, such as in the JavaScript.nbs file itself. Here, among other things, we find the following:

    COMPLETION:js_identifier, js_keyword, js_whitespace, js_operator, js_separator, js_comment: {
    text1:org.netbeans.modules.languages.javascript.JavaScript.completionItems;
    confirmChars:";(,.";
    }

    So, here various grammar rules have their code completion story defined in a class called 'JavaScript', in the package 'org.netbeans.modules.languages.javascript', which is this one.

  14. First, create a class called Groovy, in your main package, and then copy the following code, which is the code completion section from the file above, together with some supporting methods and slightly tweaked declarations, into the Groovy class:

    public class Groovy {
    private static final String DOC = "org/netbeans/modules/mygroovyeditor/Documentation.xml";
    private static final String DOM0 = "org/netbeans/modules/mygroovyeditor/DOM0.xml";
    private static final String DOM1 = "org/netbeans/modules/mygroovyeditor/DOM1.xml";
    private static final String DOM2 = "org/netbeans/modules/mygroovyeditor/DOM2.xml";
    private static LibrarySupport library;
    private static LibrarySupport getLibrary () {
    if (library == null)
    library = LibrarySupport.create (
    Arrays.asList (new String[] {DOC, DOM0, DOM1, DOM2})
    );
    return library;
    }
    // code completion .........................................................
    public static List completionItems (Context context) {
    if (context instanceof SyntaxContext) {
    List result = new ArrayList ();
    return merge (result);
    }
    List result = new ArrayList ();
    TokenSequence ts = context.getTokenSequence ();
    Token token = previousToken (ts);
    String tokenText = token.text ().toString ();
    String libraryContext = null;
    if (tokenText.equals ("new")) {
    result.addAll (getLibrary ().getCompletionItems ("constructor"));
    return merge (result);
    }
    if (tokenText.equals (".")) {
    token = previousToken (ts);
    if (token.id ().name ().endsWith ("identifier"))
    libraryContext = token.text ().toString ();
    } else
    if (token.id ().name ().endsWith ("identifier") ) {
    token = previousToken (ts);
    if (token.text ().toString ().equals (".")) {
    token = previousToken (ts);
    if (token.id ().name ().endsWith ("identifier"))
    libraryContext = token.text ().toString ();
    } else
    if (token.text ().toString ().equals ("new")) {
    result.addAll (getLibrary ().getCompletionItems ("constructor"));
    return merge (result);
    }
    }
    if (libraryContext != null) {
    result.addAll (getLibrary ().getCompletionItems (libraryContext));
    result.addAll (getLibrary ().getCompletionItems ("member"));
    } else
    result.addAll (getLibrary ().getCompletionItems ("root"));
    return merge (result);
    }
    private static List merge (List items) {
    Map map = new HashMap ();
    Iterator it = items.iterator ();
    while (it.hasNext ()) {
    CompletionItem completionItem = it.next ();
    CompletionItem current = map.get (completionItem.getText ());
    if (current != null) {
    String library = current.getLibrary ();
    if (library == null) library = "";
    if (completionItem.getLibrary () != null &&
    library.indexOf (completionItem.getLibrary ()) < 0
    )
    library += ',' + completionItem.getLibrary ();
    completionItem = CompletionItem.create (
    current.getText (),
    current.getDescription (),
    library,
    current.getType (),
    current.getPriority ()
    );
    }
    map.put (completionItem.getText (), completionItem);
    }
    return new ArrayList (map.values ());
    }
    private static Token previousToken (TokenSequence ts) {
    do {
    if (!ts.movePrevious ()) return ts.token ();
    } while (
    ts.token ().id ().name ().endsWith ("whitespace") ||
    ts.token ().id ().name ().endsWith ("comment")
    );
    return ts.token ();
    }
    }

  15. Set a dependency on Lexer, which should make all the scary red lines go away, and copy the content of Documentation.xml, Dom0.xml, Dom1.xml, and Dom2.xml into files of the same name in your main package. Another place to access them all from is here.

  16. Next, in our Groovy.nbs file, rewrite the JavaScript code completion declaration to the following:

    COMPLETION:identifier, keyword, whitespace, operator, separator, comment: {
    text1:org.netbeans.modules.mygroovyeditor.Groovy.completionItems;
    confirmChars:";(,.";
    }

  17. Install the editor again and now notice the following:

  18. So, the code completion is working. Now we need to edit the XML files we created above, to provide just the values that we need. Understandably, this could be a lot of work. There are values that are 'built in' to a language, and then there are those that are generated from libraries. Built-in values are, for example, the keywords. Those coming from libraries are, for example, Swing components, methods, and properties. For the latter, there is some kind of complex (or so it seems) approach relating to the Common Scripting Language Platform Support, by Tor, which was used for Ruby and which Caoyuan Deng used for Erlang. However, I don't understand it yet, and it doesn't seem stable, so I'm not making use of it yet. It provides an indexing mechanism for parsing and retrieving items from a Lucene engine, if I understand it correctly. However, the JavaScript language support relies purely on the XML approach outlined above, so doing it that way does seem to be possible.

  19. Let's generate an XML file containing Swing components and Swing methods. The following is a complete Generator class, received from Jim Clarke, which in this case generates the XML content of a new file, called "DOM3.XML":

    public class Main {
    private static Map map = new HashMap();
    static {
    // swing components
    map.put("button", JButton.class);
    map.put("buttonGroup", ButtonGroup.class);
    map.put("checkBox", JCheckBox.class);
    map.put("checkBoxMenuItem", JCheckBoxMenuItem.class);
    map.put("colorChooser", JColorChooser.class);
    map.put("comboBox", JComboBox.class);
    map.put("desktopPane", JDesktopPane.class);
    map.put("dialog", JDialog.class);
    map.put("editorPane", JEditorPane.class);
    map.put("fileChooser", JFileChooser.class);
    map.put("formattedTextField", JFormattedTextField.class);
    map.put("frame", JFrame.class);
    map.put("internalFrame", JInternalFrame.class);
    map.put("label", JLabel.class);
    map.put("layeredPane", JLayeredPane.class);
    map.put("list", JList.class);
    map.put("menu", JMenu.class);
    map.put("menuBar", JMenuBar.class);
    map.put("menuItem", JMenuItem.class);
    map.put("optionPane", JOptionPane.class);
    map.put("panel", JPanel.class);
    map.put("passwordField", JPasswordField.class);
    map.put("popupMenu", JPopupMenu.class);
    map.put("progressBar", JProgressBar.class);
    map.put("radioButton", JRadioButton.class);
    map.put("radioButtonMenuItem", JRadioButtonMenuItem.class);
    map.put("scrollBar", JScrollBar.class);
    map.put("scrollPane", JScrollPane.class);
    map.put("separator", JSeparator.class);
    map.put("slider", JSlider.class);
    map.put("spinner", JSpinner.class);
    map.put("splitPane", JSplitPane.class);
    map.put("tabbedPane", JTabbedPane.class);
    map.put("table", JTable.class);
    map.put("textArea", JTextArea.class);
    map.put("textField", JTextField.class);
    map.put("textPane", JTextPane.class);
    map.put("toggleButton", JToggleButton.class);
    map.put("toolBar", JToolBar.class);
    map.put("tree", JTree.class);
    map.put("viewport", JViewport.class);
    map.put("window", JWindow.class);
    // layouts
    map.put("borderLayout", BorderLayout.class);
    map.put("boxLayout", BoxLayout.class);
    map.put("cardLayout", CardLayout.class);
    map.put("flowLayout", FlowLayout.class);
    map.put("gridBagLayout", GridBagLayout.class);
    map.put("gridBagConstraints", GridBagConstraints.class); // "gbc" alias for "gridBagConstraints""
    map.put("gridLayout", GridLayout.class);
    map.put("overlayLayout", OverlayLayout.class);
    map.put("springLayout", SpringLayout.class);
    // map.put("tableLayout", ???); No mapped class
    map.put("hbox", Box.class);
    map.put("vbox", Box.class);
    map.put("hglue", Component.class);
    map.put("hstrut", Component.class);
    map.put("vglue", Component.class);
    map.put("vstruct", Component.class);
    map.put("glue", Component.class);
    map.put("ridgerea", Component.class);
    }
    /\*\* Creates a new instance of Generator \*/
    public Main() {
    try {
    for (String swingBuilderName : map.keySet()) {
    genBean(swingBuilderName, map.get(swingBuilderName));
    }
    } catch (IntrospectionException ex) {
    ex.printStackTrace();
    }
    }
    protected void genBean(String swingBuilderName, Class beanClass) throws IntrospectionException {
    BeanInfo info = Introspector.getBeanInfo(beanClass, Object.class);
    MethodDescriptor[] properties = info.getMethodDescriptors();
    if (!swingBuilderName.equals("")) {
    System.out.println("<node key=\\"" + swingBuilderName + "\\" context=\\"root\\" library=\\"Swing component\\" type=\\"interface\\"/>");
    }
    for (int i = 0; i < properties.length; i++) {
    if (!properties[i].isHidden() && properties[i].getMethod() != null) {
    if (i > 0) {
    System.out.println(" <node key=\\"" + properties[i].getName() + "\\" context=\\"member\\" library=\\"Swing method\\" type=\\"method\\"/>");
    }
    }
    }
    }
    public static void main(String[] args) {
    new Main();
    }
    }

    The Output window now contains a very long list of XML tags. Create DOM3.xml and paste the content into that file. Again, this should be done in a different way, not via a documentation XML file, but the other way doesn't make sense to me yet and doesn't seem stable (requiring hacks in various places).

  20. Tweaking the above generator slightly, you can use it to generate the following:

    TOKEN:swingComponents:(
    "table" |
    "checkBoxMenuItem" |
    "dialog" |
    "layeredPane" |
    "tree" |
    "hstrut" |
    "button" |
    "gridBagLayout" |
    "gridLayout" |
    "textField" |
    "vglue" |
    "viewport" |
    "menuItem" |
    "menu" |
    "window" |
    "fileChooser" |
    "toggleButton" |
    "menuBar" |
    "ridgerea" |
    "checkBox" |
    "tabbedPane" |
    "springLayout" |
    "splitPane" |
    "boxLayout" |
    "textPane" |
    "cardLayout" |
    "overlayLayout" |
    "textArea" |
    "popupMenu" |
    "flowLayout" |
    "hglue" |
    "scrollBar" |
    "spinner" |
    "comboBox" |
    "buttonGroup" |
    "hbox" |
    "gridBagConstraints" |
    "desktopPane" |
    "scrollPane" |
    "separator" |
    "vbox" |
    "passwordField" |
    "toolBar" |
    "radioButton" |
    "optionPane" |
    "radioButtonMenuItem" |
    "list" |
    "editorPane" |
    "vstruct" |
    "borderLayout" |
    "frame" |
    "internalFrame" |
    "label" |
    "progressBar" |
    "colorChooser" |
    "slider" |
    "glue" |
    "formattedTextField" |
    "panel"
    )

  21. So paste the above into your Groovy.nbs file and declare your new DOM3.xml file:

    private static final String DOM3 = "org/netbeans/modules/mygroovyeditor/DOM3.xml";

    Then add DOM3 to your getLibrary() method:

    private static LibrarySupport getLibrary () {
    if (library == null)
    library = LibrarySupport.create (
    Arrays.asList (new String[] {DOC, DOM0, DOM1, DOM2, DOM3})
    );
    return library;
    }

    And also declare a color for your Swing components:

    COLOR:swingComponents: {
    foreground_color: "magenta";
    font_type: "bold";
    }

    And add the swingComponents keyword to the COMPLETION declaration:

    COMPLETION:identifier, keyword, keywordGroovy, whitespace, operator, separator, comment, swingComponents: {
    text1:org.netbeans.modules.mygroovyeditor.Groovy.completionItems;
    confirmChars:";(,.";
    }

  22. Install the editor again and now notice the following in the third template:

    Notice how the Swing components have a distinct color and that you can call up code completion on them, in this case, in the screenshot above, on the 'panel' component.

In the editor, many others things can be added, such as actions, hyperlinks, and navigator support. Let's leave all that for another day. Clearly the basis of a Groovy editor is laid out above. Who will pick it up and run with it?

Join the discussion

Comments ( 15 )
  • Thomas Zillinger Thursday, August 23, 2007

    RunForestRun! That's an amazing and really simple editor intro, thanks geertjan! How 'bout posting this to http://wiki.netbeans.org/wiki/view/CommunityDocs.

    Is it also possible to configure Schliemann to have a docu (eg. javadoc) open when you navigate through code-complition?


  • Gregg Sporar Thursday, August 23, 2007

    Cool. Where can I get the bits? :-)


  • Geertjan Thursday, August 23, 2007

    Thomas, this is only part 1, so watch this space. About JavaDoc, I read somewhere that you need to implement CompletionProvider, i.e., that it isn't directly supported by Schliemann itself.

    Greg, the bits? :-) Just follow the steps in this blog entry and you'll have them! Everything is included here, I have (as far as I am aware) not left out a single step nor assumed any prior knowledge of any shape or color.


  • Geertjan Thursday, August 23, 2007

    Oops, sorry, I meant "Gregg".


  • Stefan Thursday, August 23, 2007

    I want the bits too ;-)

    It would be great when there would be a simple Groovy plugin for NetBeans available, also when it contains only this functionality. It's much better then nothing today :-(


  • Ranganath.S Friday, August 24, 2007

    hi,

    I just followed the above steps, its really an good information. But i did had some problems, kindly let me know where have i gone wrong.

    1) I am getting error in the following code snippets in nbs file.

    2) I am also getting the errrorin my Groovy1.groovy files saying LBL_UNPAIRED_END_TAG.

    TOKEN:string: (

    ["\\""]|["'"]

    ([\^ "\\"" "\\n" "\\r"] |

    ("\\\\"<-(I am gettin error here) ["r" "n" "t" "\\\\" "\\'" "\\""])

    )\*

    ["\\""]|["'"]

    )

    TOKEN:char: (

    "\\'"

    ( [\^"\\'" "\\n" "\\r"] |

    ("\\\\"<-(I am gettin error here) ["r" "n" "t" "\\\\" "\\'" "\\""])

    )

    "\\'"

    )

    Thank you


  • Geertjan Friday, August 24, 2007

    Hi Raganath. Those are the harmless error messages I referred to in step 4. I haven't looked at fixing them, don't think they really matter. Just leave them like they are, or fix them, up to you, will not impact the result. Read step 4 again.


  • Geertjan Friday, August 24, 2007

    Plus, step 4 tells you to delete the ERROR declarations in Groovy.nbs. So, I suggest, go back to step 4, and follow those instructions.


  • Geertjan Friday, August 24, 2007

    Sorry, didn't spell your name right. I meant to address you as "Ranganath"!


  • Ranganaath.S Monday, August 27, 2007

    hi,

    Thanks a lot man. its working fine N its a very good documentation.

    -Ranganath.S


  • Geertjan Monday, August 27, 2007

    Ranganath, does that mean it is now working for you? Very cool, if so. Would be great to read about it in your blog, with pics and everything!


  • Leonardo Thursday, September 13, 2007

    Awesome!

    I'm writing an editor to JBoss' Drools DSL and this post was extreme helpful.

    Thanks!


  • Geertjan Thursday, September 13, 2007

    Excellent! Please send me an e-mail (geertjan DOT wielenga AT sun DOT com) and I will be happy to include you in an article I am writing about people who are making use of Schliemann.


  • marina Monday, October 27, 2008

    Hello!

    I need help. I Want to import content of one panel into my NetBeans Platform. How can I do that?

    Thank you, Student


  • sojin Tuesday, March 31, 2009

    getTokenSequence() has been removed from Context, So we might end up to change the code as follows,

    ............

    ............

    List result = new ArrayList();

    //TokenSequence ts = context.getTokenSequence ();

    AbstractDocument document = (AbstractDocument) context.getDocument();

    document.readLock();

    try {

    TokenSequence ts = TokenHierarchy.get(context.getDocument()).tokenSequence();

    ts.move(context.getOffset());

    Token token = previousToken(ts);

    String tokenText = token.text().toString();

    String libraryContext = null;

    if (tokenText.equals("new")) {

    result.addAll(getLibrary().getCompletionItems("constructor"));

    return merge(result);

    }

    ...........

    ...........

    ...........

    result.addAll(getLibrary().getCompletionItems("root"));

    }

    return merge(result);

    } finally {

    document.readUnlock();

    }


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha
Oracle

Integrated Cloud Applications & Platform Services