TextEditor Framework with e4 and JavaFX (including jdt and nashorn)


Tonight I had some time to hack on a research project which uses the StyledTextControl provided by e(fx)clipse. Syntax highlighting for the basic languages like Java, JavaScript, XML and groovy has already been implemented since a long time.

So next on my list was support for outlines which turned out to be fairly easy. Watch the video to see it in action.

If you watched the video you noticed that while a generic outline for Java makes always sense for JavaScript it does not necessarily because frameworks invented their own way to define a class. In my example I used qooxdoo – because I knew it from earlier work – but the same applies for others as well.

Java

For Java I simply added org.eclipse.jdt.core and the main logic then looks like this:

	@Override
	public Outline createOutline(Input<?> input) {
		ASTParser parser = ASTParser.newParser(AST.JLS8);
		parser.setKind(ASTParser.K_COMPILATION_UNIT);
	    parser.setSource(input.getData().toString().toCharArray());
	    parser.setResolveBindings(true);
	    ASTNode cu = parser.createAST(null);

	    Stack<OutlineItem> i = new Stack<>();
        i.push(new JavaOutlineItem("<root>",null));

	    cu.accept(new ASTVisitor() {

	    	@Override
	    	public boolean visit(TypeDeclaration node) {
	    		OutlineItem o = new JavaOutlineItem(node.getName().getFullyQualifiedName(), "java-class");
	    		i.peek().getChildren().add(o);
	    		i.push(o);

	    		return super.visit(node);
	    	}

	    	@Override
	    	public void endVisit(TypeDeclaration node) {
	    		i.pop();
	    		super.endVisit(node);
	    	}

	    	@Override
	    	public boolean visit(FieldDeclaration node) {
	    		for( Object v : node.fragments() ) {
	    			if( v instanceof VariableDeclarationFragment ) {
	    				VariableDeclarationFragment vdf = (VariableDeclarationFragment) v;
	    				i.peek().getChildren().add(new JavaOutlineItem(vdf.getName().getFullyQualifiedName(), "java-field"));
	    			}
	    		}
	    		return super.visit(node);
	    	}

	    	@Override
	    	public boolean visit(MethodDeclaration node) {
	    		i.peek().getChildren().add(new JavaOutlineItem(node.getName().getFullyQualifiedName(), "java-method"));
	    		return super.visit(node);
	    	}

		});
		return new JavaOutline(i.peek());
	}

JavaScript

We only look at the enhanced outline contributed to the framework. To understand it better here’s some simple background:

  1. A class definition is done with qx.Class.define
  2. Properties are defined in the properties-Attribute
  3. Members are defined in the members-Attribute
  4. Names with _ means protected, __ means private

So I used Nashorn to parse the JavaScript files like this:

@Override
  public Outline createOutline(Input<?> input) {
    final Options options = new Options("nashorn");
    options.set("anon.functions", true);
    options.set("parse.only", true);
    options.set("scripting", true);

    ErrorManager errors = new ErrorManager();
    Context context = new Context(options, errors, Thread.currentThread().getContextClassLoader());

    Context.setGlobal(new Global( context ));

    final Source   source   = Source.sourceFor("dummy.js", input.getData().toString().toCharArray());
    FunctionNode node = new Parser(context.getEnv(), source, errors).parse();

    JSOutlineItem root = new JSOutlineItem("<root>",null);

    node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
			private JSOutlineItem classDef;

			@Override
			public boolean enterCallNode(CallNode callNode) {
				if( callNode.getFunction().toString().endsWith("qx.Class.define") ) {
					classDef = new JSOutlineItem(((LiteralNode<?>)callNode.getArgs().get(0)).getString(), "qx-class-def");
					root.getChildren().add(classDef);
				}
				return super.enterCallNode(callNode);
			}

			@Override
			public boolean enterPropertyNode(PropertyNode propertyNode) {
				if( classDef != null ) {
					switch (propertyNode.getKeyName()) {
					case "include":
						break;
					case "extend":
						break;
					case "construct":
						break;
					case "statics":
						break;
					case "events":
						break;
					case "properties":
						classDef.getChildren().add(handleProperties(propertyNode));
						break;
					case "members":
						classDef.getChildren().add(handleMembers(propertyNode));
						break;
					default:
						break;
					}
				}
				return super.enterPropertyNode(propertyNode);
			}
		});
		return new JSOutline(root);
	}

	private JSOutlineItem handleProperties(PropertyNode p) {
		JSOutlineItem outline = new JSOutlineItem("Properties", "qx-properties");
		p.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
			@Override
			public boolean enterPropertyNode(PropertyNode propertyNode) {
				if( p != propertyNode ) {
					outline.getChildren().add(new JSOutlineItem(propertyNode.getKeyName(), "qx-property-"+visibility(propertyNode.getKeyName())));
					return false;
				}

				return true;
			}
		});
		return outline;
	}

	private JSOutlineItem handleMembers(PropertyNode p) {
		JSOutlineItem outline = new JSOutlineItem("Members", "qx-members");
		p.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
			@Override
			public boolean enterPropertyNode(PropertyNode propertyNode) {
				if( p != propertyNode ) {
					if( propertyNode.getValue() instanceof FunctionNode ) {
						outline.getChildren().add(new JSOutlineItem(propertyNode.getKeyName()+"()","qx-method-"+visibility(propertyNode.getKeyName())));
					} else if( propertyNode.getValue() instanceof ObjectNode ) {
						outline.getChildren().add(new JSOutlineItem(propertyNode.getKeyName(),"qx-field-"+visibility(propertyNode.getKeyName())));
					} else if( propertyNode.getValue() instanceof LiteralNode<?> ) {
						outline.getChildren().add(new JSOutlineItem(propertyNode.getKeyName(),"qx-field-"+visibility(propertyNode.getKeyName())));
					} else {
						System.err.println("Unknown value type: " + propertyNode.getValue().getClass());
					}
					return false;
				}
				return true;
			}
		});
		return outline;
	}

	private static String visibility(String name) {
		if( name.startsWith("__") ) {
			return "private";
		} else if( name.startsWith("_") ) {
			return "protected";
		}
		return "public";
	}

Next on my list are improvements for the editor-control which still lacks some features and afterwards I want:

  1. Syntax hightlighting for C/C++
  2. Syntax hightlighting for TypeScript
  3. Syntax hightlighting for Swift
  4. Error and warnings for Java, JavaScipt and TypeScript
  5. Auto-Complete for Java
This entry was posted in e(fx)clipse. Bookmark the permalink.

One Response to TextEditor Framework with e4 and JavaFX (including jdt and nashorn)

  1. Pingback: JavaFX links of the week, December 1 // JavaFX News, Demos and Insight // FX Experience

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