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.
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.
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"
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.
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.
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.
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:
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:
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.
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:
COMPLETION:js_identifier, js_keyword, js_whitespace, js_operator, js_separator, js_comment: {
text1:org.netbeans.modules.languages.javascript.JavaScript.completionItems;
confirmChars:";(,.";
}
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 ListcompletionItems (Context context) {
if (context instanceof SyntaxContext) {
Listresult = new ArrayList ();
return merge (result);
}
Listresult = 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 Listmerge (List items) {
Mapmap = new HashMap ();
Iteratorit = 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 ();
}
}
COMPLETION:identifier, keyword, whitespace, operator, separator, comment: {
text1:org.netbeans.modules.mygroovyeditor.Groovy.completionItems;
confirmChars:";(,.";
}
public class Main {
private static Mapmap = 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).
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"
)
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:";(,.";
}
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?
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?
Cool. Where can I get the bits? :-)
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.
Oops, sorry, I meant "Gregg".
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 :-(
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
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.
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.
Sorry, didn't spell your name right. I meant to address you as "Ranganath"!
hi,
Thanks a lot man. its working fine N its a very good documentation.
-Ranganath.S
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!
Awesome!
I'm writing an editor to JBoss' Drools DSL and this post was extreme helpful.
Thanks!
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.
Hello!
I need help. I Want to import content of one panel into my NetBeans Platform. How can I do that?
Thank you, Student
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();
}