In my last blog post I introduced the DSL we’ll ship with e(fx)clipse 2.1 in August 2015.
Our main deployment platform is of course e4 on JavaFX but because we have a clean architecture based on IoC and services our components don’t know about OSGi and hence can be used in any Java(FX) application no matter if you run on OSGi or not.
To show you that those are not idle words the first blog showing the code editor components in action uses plain Java and maven as build tool – for those who want to use them in OSGi a blog will follow soon.
The following video demonstrates the final application in action
Step 1: Install e(fx)clipse 2.1 or later
At the time of this writing e(fx)clipse 2.1 has not been released so the best option to get started is to download our all-in-one nightly build.
Step 2: Create a new maven project
Step 3: Modify the pom.xml
First we need modify the source and target version for the Java compiler by adding:
<!-- ... --> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> <!-- ... -->
Because the dependencies have not yet been released we need to add the Sonatype snapshot repository with:
<!-- ... --> <repositories> <repository> <id>sonatype-snapshots</id> <url>https://oss.sonatype.org/content/repositories/snapshots/</url> </repository> </repositories> <!-- ... -->
And finally we add the JavaFX-Code editor component with:
<!-- ... --> <dependencies> <dependency> <groupId>at.bestsolution.eclipse</groupId> <artifactId>org.eclipse.fx.code.editor.fx</artifactId> <version>2.1.0-SNAPSHOT</version> </dependency> </dependencies> <!-- ... -->
At the end your pom.xml should look like this:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>at.bestsolution.sample</groupId> <artifactId>at.bestsolution.sample.code</artifactId> <version>0.0.1-SNAPSHOT</version> <repositories> <repository> <id>sonatype-snapshots</id> <url>https://oss.sonatype.org/content/repositories/snapshots/</url> </repository> </repositories> <dependencies> <dependency> <groupId>at.bestsolution.eclipse</groupId> <artifactId>org.eclipse.fx.code.editor.fx</artifactId> <version>2.1.0-SNAPSHOT</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
Step 4: Language definition
Now that we have configured our project appropriately we can start defining our application. We start with creating a package like “at.bestsolution.sample.code
” and there we add a file named “dart.ldef
”
After the file is created. Eclipse will prompt you for adding the Xtext nature to your project because of course the DSL is implemented with the help of Xtext.
The last step before we start defining our language is to make some modifications to the project. So open the project properties and navigate to LDef/Compiler
check the “Enable project specific settings” and modify the Output Folder/Directory
value to src/main/java
Now paste the following content to the dart.ldef file:
package at.bestsolution.sample.code dart { partitioning { partition __dftl_partition_content_type partition __dart_singlelinedoc_comment partition __dart_multilinedoc_comment partition __dart_singleline_comment partition __dart_multiline_comment partition __dart_string rule { single_line __dart_string "'" => "'" single_line __dart_string '"' => '"' single_line __dart_singlelinedoc_comment '///' => '' single_line __dart_singleline_comment '//' => '' multi_line __dart_multilinedoc_comment '/**' => '*/' multi_line __dart_multiline_comment '/*' => '*/' } } lexical_highlighting { rule __dftl_partition_content_type whitespace javawhitespace { default dart_default dart_operator { character [ ';', '.', '=', '/', '\\', '+', '-', '*', '<', '>', ':', '?', '!', ',', '|', '&', '^', '%', '~' ] } dart_bracket { character [ '(', ')', '{', '}', '[', ']' ] } dart_keyword { keywords [ "break", "case", "catch", "class", "const", "continue", "default" , "do", "else", "enum", "extends", "false", "final", "finally", "for" , "if", "in", "is", "new", "null", "rethrow", "return", "super" , "switch", "this", "throw", "true", "try", "var", "void", "while" , "with" ] } dart_keyword_1 { keywords [ "abstract", "as", "assert", "deferred" , "dynamic", "export", "external", "factory", "get" , "implements", "import", "library", "operator", "part", "set", "static" , "typedef" ] } dart_keyword_2 { keywords [ "async", "async*", "await", "sync*", "yield", "yield*" ] } dart_builtin_types { keywords [ "num", "String", "bool", "int", "double", "List", "Map" ] } } rule __dart_singlelinedoc_comment { default dart_doc dart_doc_reference { single_line "[" => "]" } } rule __dart_multilinedoc_comment { default dart_doc dart_doc_reference { single_line "[" => "]" } } rule __dart_singleline_comment { default dart_single_line_comment } rule __dart_multiline_comment { default dart_multi_line_comment } rule __dart_string { default dart_string dart_string_inter { single_line "${" => "}" //TODO We need a $ => IDENTIFIER_CHAR rule } } } integration { javafx { java "at.bestsolution.sample.code.generated" } } }
I won’t explain most of the files content because I’ve done that already in the last blog post “Defining source editors with a DSL” but in brief it holds the paritioning and tokenizing rules required to provide lexical highlighting for Google Dart.
The only new section is:
package org.eclipse.fx.code.dart dart { .... integration { javafx { java "at.bestsolution.sample.code.generated" } } }
This section holds the configuration for the code generator. In our case we instruct it to generate some java code for us and your project explorer should show something similar to this.
For us the 2 most important classes are:
DartPartitioner
: This one is responsible for partitioning your source file into eg comment-sections, code-sections, string-sections, …DartPresentationReconciler
: This one is responsible to tokenize the different partitions eg to create keyword-tokens, ….
Step 5: Setup an editor control
Now that the parsing infrastructure is in place we can create our setup for the editor control. For that we create a new class “DartEditor
” in “at.bestsolution.sample.code
” and make it extend “org.eclipse.fx.code.editor.fx.TextEditor
“.
package at.bestsolution.sample.code; import org.eclipse.fx.code.editor.StringInput; public class DartEditor extends TextEditor { public DartEditor(StringInput input) { setInput(input); setDocument(new InputDocument(input)); setPartitioner(new DartPartitioner()); setSourceViewerConfiguration( new DefaultSourceViewerConfiguration(input, new DartPresentationReconciler(), null, null, null) ); } }
The above is the minimal configuration you need when creating an editor:
org.eclipse.fx.code.editor.Input
: Is the abstraction for the retrieving and storing the content eg on the filesystem, …org.eclipse.jface.text.Document
: Is the text buffer behind the text editororg.eclipse.jface.text.IDocumentPartitioner
: The component responsible to partition the source fileorg.eclipse.jface.text.source.SourceViewerConfiguration
: The component responsible for syntax highlighting, error displaying and auto-completion
The final application
Now that we have an editor component we can build our final application which needs to have a filesystem browser on the left and a tab folder with editors on the right.
package at.bestsolution.sample.code; import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import org.eclipse.fx.code.editor.SourceFileInput; import org.eclipse.fx.ui.controls.filesystem.FileItem; import org.eclipse.fx.ui.controls.filesystem.ResourceEvent; import org.eclipse.fx.ui.controls.filesystem.ResourceItem; import org.eclipse.fx.ui.controls.filesystem.ResourceTreeView; import javafx.application.Application; import javafx.beans.binding.Bindings; import javafx.beans.binding.StringExpression; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.collections.FXCollections; import javafx.event.ActionEvent; import javafx.scene.Scene; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.layout.BorderPane; import javafx.stage.DirectoryChooser; import javafx.stage.Stage; public class DartEditorSample extends Application { private TabPane tabFolder; private ResourceTreeView viewer; static class EditorData { final Path path; final DartEditor editor; public EditorData(Path path, DartEditor editor) { this.path = path; this.editor = editor; } } @Override public void start(Stage primaryStage) throws Exception { BorderPane p = new BorderPane(); p.setTop(createMenuBar()); viewer = new ResourceTreeView(); viewer.addEventHandler(ResourceEvent.openResourceEvent(), this::handleOpenResource); p.setLeft(viewer); tabFolder = new TabPane(); p.setCenter(tabFolder); Scene s = new Scene(p, 800, 600); s.getStylesheets().add(getClass().getResource("default.css").toExternalForm()); primaryStage.setScene(s); primaryStage.show(); } private MenuBar createMenuBar() { MenuBar bar = new MenuBar(); Menu fileMenu = new Menu("File"); MenuItem rootDirectory = new MenuItem("Select root folder ..."); rootDirectory.setOnAction(this::handleSelectRootFolder); MenuItem saveFile = new MenuItem("Save"); saveFile.setAccelerator(new KeyCodeCombination(KeyCode.S,KeyCombination.META_DOWN)); saveFile.setOnAction(this::handleSave); fileMenu.getItems().addAll(rootDirectory, saveFile); bar.getMenus().add(fileMenu); return bar; } private void handleSelectRootFolder(ActionEvent e) { DirectoryChooser chooser = new DirectoryChooser(); File directory = chooser.showDialog(viewer.getScene().getWindow()); if( directory != null ) { viewer.setRootDirectories( FXCollections.observableArrayList(ResourceItem.createObservedPath(Paths.get(directory.getAbsolutePath())))); } } private void handleSave(ActionEvent e) { Tab t = tabFolder.getSelectionModel().getSelectedItem(); if( t != null ) { ((EditorData)t.getUserData()).editor.save(); } } private void handleOpenResource(ResourceEvent<ResourceItem> e) { e.getResourceItems() .stream() .filter( r -> r instanceof FileItem) .map( r -> (FileItem)r) .filter( r -> r.getName().endsWith(".dart")) .forEach(this::handle); } private void handle(FileItem item) { Path path = (Path) item.getNativeResourceObject(); Tab tab = tabFolder.getTabs().stream().filter( t -> ((EditorData)t.getUserData()).path.equals(path) ).findFirst().orElseGet(() -> { return createAndAttachTab(path, item); }); tabFolder.getSelectionModel().select(tab); } private Tab createAndAttachTab(Path path, FileItem item) { BorderPane p = new BorderPane(); DartEditor editor = new DartEditor(new SourceFileInput(path, StandardCharsets.UTF_8)); editor.initUI(p); ReadOnlyBooleanProperty modifiedProperty = editor.modifiedProperty(); StringExpression titleText = Bindings.createStringBinding(() -> { return modifiedProperty.get() ? "*" : ""; }, modifiedProperty).concat(item.getName()); Tab t = new Tab(); t.textProperty().bind(titleText); t.setContent(p); t.setUserData(new EditorData(path, editor)); tabFolder.getTabs().add(t); return t; } public static void main(String[] args) { Application.launch(args); } }
The last thing that needs to be done is to fill the “at/bestsolution/sample/code/default.css
” with life:
.styled-text-area .list-view { -fx-background-color: white; } .styled-text-area .dart.dart_default { -styled-text-color: rgb(0, 0, 0); } .styled-text-area .dart.dart_operator { -styled-text-color: rgb(0, 0, 0); } .styled-text-area .dart.dart_bracket { -styled-text-color: rgb(0, 0, 0); } .styled-text-area .dart.dart_keyword { -styled-text-color: rgb(127, 0, 85); -fx-font-weight: bold; } .styled-text-area .dart.dart_keyword_1 { -styled-text-color: rgb(127, 0, 85); -fx-font-weight: bold; } .styled-text-area .dart.dart_keyword_2 { -styled-text-color: rgb(127, 0, 85); -fx-font-weight: bold; } .styled-text-area .dart.dart_single_line_comment { -styled-text-color: rgb(63, 127, 95); } .styled-text-area .dart.dart_multi_line_comment { -styled-text-color: rgb(63, 127, 95); } .styled-text-area .dart.dart_string { -styled-text-color: rgb(42, 0, 255); } .styled-text-area .dart.dart_string_inter { -styled-text-color: rgb(42, 0, 255); -fx-font-weight: bold; } .styled-text-area .dart.dart_builtin_types { -styled-text-color: #74a567; -fx-font-weight: bold; } .styled-text-area .dart.dart_doc { -styled-text-color: rgb(63, 95, 191); } .styled-text-area .dart.dart_doc_reference { -styled-text-color: rgb(63, 95, 191); -fx-font-weight: bold; }
Maven snapshot repository doesn’t have JAR files ( look at here: https://oss.sonatype.org/content/repositories/snapshots/at/bestsolution/eclipse/org.eclipse.fx.code.editor.fx/2.1.0-SNAPSHOT/ ) This is cause the following error:
[ERROR] Failed to execute goal on project application: Could not resolve dependencies for project keylord:application:jar:1.3.0: The following artifacts could not be resolved: at.bestsolution.eclipse:org.eclipse.fx.code.editor.fx:jar:2.1.0-SNAPSHOT, at.bestsolution.eclipse:org.eclipse.fx.code.editor:jar:2.1.0-SNAPSHOT, at.bestsolution.eclipse:org.eclipse.fx.core.di:jar:2.1.0-SNAPSHOT, at.bestsolution.eclipse:org.eclipse.fx.text:jar:2.1.0-SNAPSHOT, at.bestsolution.eclipse:org.eclipse.fx.text.ui:jar:2.1.0-SNAPSHOT, at.bestsolution.eclipse:org.eclipse.fx.ui.controls:jar:2.1.0-SNAPSHOT, at.bestsolution.eclipse:org.eclipse.fx.core:jar:2.1.0-SNAPSHOT: Could not find artifact at.bestsolution.eclipse:org.eclipse.fx.code.editor.fx:jar:2.1.0-SNAPSHOT in sonatype-snapshots (https://oss.sonatype.org/content/repositories/snapshots/) -> [Help 1]
Could you please check it?
Sorry – I was certain that all stuff is available but somehow things vanished. I’ve uploaded once more the snapshots, wiped my local directory and now things resolve fine
How can we set a wrapping width on the content of the source area?
We don’t provide word wrap a the moment – the problem is that we have to fix the cell height for now because of problems with the ListView implementation we are useing. If we’d manage to get rid of that we could implement word wrap in a breeze because a line is made up of a TextFlow who would manage that for us. Feel free to file a bug
Pingback: Access Dart Analysis Server from Java | Tomsondev Blog
Pingback: e(fx)clipse 2.1.0 released | Tomsondev Blog
hi Tom ,I can’t get org.eclipse.fx.code.editor.fx-2.1.0-SNAPSHOT.jar,but I get org.eclipse.fx.code.editor.fx-2.1.0.jar.But It seems the api has changed. because I get those error on my eclipse.
DartEditor editor = new DartEditor(new SourceFileInput(path,StandardCharsets.UTF_8));
—> Cannot instantiate the type SourceFileInput error!!
ReadOnlyBooleanProperty modifiedProperty = editor.modifiedProperty();
—> Method undefined for type error.
How do I can fix those error,and run the appliaction.
Thanks!
Right the API has changed slightly since the release – take a look at https://github.com/BestSolution-at/efxclipse-codeeditor-samples how the current code looks like
Hi Tom,
– How can I add a context menu (ex copy/cut/paste) to the editor, how to retrieve selection ?
– is multiple lines tab indent supported ?
– Where can I find the groovy ldef file mentioned in another blog post ?
– I’ll probably figure out myself how to add annotations/markers
Great project, but missing some doc to get quickly started… don’t wanna use e4 as it’s overkill for my current project.
– On context menu: Control#setContextMenu(ContextMenu)?
– On multi tab indent: would require sponsored development (if you are interested get in touch with us)
– The current set of ldefs with have can be found at http://git.eclipse.org/c/efxclipse/org.eclipse.efxclipse.git/tree/bundles/code/org.eclipse.fx.code.editor.langs/src/org/eclipse/fx/code/editor/ldef/langs
– On annotations/markers: Checkout https://github.com/BestSolution-at/eclipsemag.fx.styledtext and look at DefaultSourceViewerConfiguration who accepts IAnnotationModel and AnnotationPresenter used to create markers (we don’t yet have support for underlining inside the code but that will hopefully come very soon together with semantic highlighting)
If you have further question please ask them at https://www.eclipse.org/forums/index.php/f/259/
Have you any plans to release latest version of editor control (without e4 and OSGi)? I have looked to commit log, seems like since version 2.1.0 you have made a great work: added new features and fix a lot of bugs. Will be very interesting to look at it.
Yes an update is coming very soon including a sample repository, demonstrating the APIs, …
In case you haven’t noticed – https://tomsondev.bestsolution.at/2017/01/26/make-it-easy-to-consume-efxclipse-libraries-in-maven-or-gradle/
This looks really cool, I am propably going to rewrite my mini IDE for a custom language using this. I am also new to JavaFX so this might be a dump question, but how do I add undo-redo functionality for ctrl+z and ctrl+y.
Also how do Ito set the font family for the whole source (including whitespace)? I tried the following, but it didn’t work for whitespace:
“`
.styled-text-area {
-fx-font-family: Consolas;
}
.styled-text-area .list-view {
-fx-font-family: Consolas;
-fx-background-color: white;
}
.styled-text-area .mpl.mpl_default {
-fx-font-family: Consolas;
-styled-text-color: rgb(0, 0, 0);
}
.styled-text-area .mpl.mpl_keyword {
-fx-font-family: Consolas;
-styled-text-color: rgb(127, 0, 85);
-fx-font-weight: bold;
}
…
“`
* You can get the IUndoManager from SourceViewerConfiguration#getUndoManager
* Your CSS looks correct not sure why it does not work
I was using 2.2.0 but i found 2.5.0 now and everything works perfectly đŸ˜€
Hi Tom, I am creating a JavaFX e4 application. I wanted to add project explorer view. What approach do you suggest I do not find org.eclipse.ui in my dependencies.
So it is based on the Eclipse-Filesystem (Core-Resources) or based upon NIO
Hi Tom, i have some trouble to find a maven build for the Ldef code generation. I didn’t find anything to let maven generate the source code.