8

Previously in Swing, I have used the JSyntaxPane for making a tiny Java source editor. For practice, I decided to redo the entire project in JavaFX and adding support for more languages. Preferably, as many as I can.

However, there seems to be nothing similar to JSyntaxPane.

A bit of research led me to Tom Schindl's blog where he has made a source code viewer with proper syntax highlighting. No editing support, sadly.

Then there is JewelSea's blog but from the screenshot it look's like SO's type-and-preview method. Not something desired in a code editor.

Again, from JFXperience I found that highlighting and indenting and editing panel / node will be available in JavaFX 8 and it will also allow embedding Swing into Java.

Till then, what are my other options ?

I know JavaFX can interoperate with JavaScript so is there a way I can use some JavaScript library to accomplish the same?

An SO User
  • 24,612
  • 35
  • 133
  • 221

4 Answers4

14

There's RichTextFX which lets you do the highlighting. Check out the Java Keywords example.

Note that it requires JDK8.

Saikat
  • 14,222
  • 20
  • 104
  • 125
Tomas Mikula
  • 6,537
  • 25
  • 39
  • I am trying to add codeArea into Tab under splitpane and applying the css file to codeArea. Line no.s are missing in the code area. I know you have applied the css file to scene, that may be difference, but is there anyway i can use lineno's in codeArea. :) BTW thanks for RichTextFX, it is very beautiful and very helpful. :) – DeepSidhu1313 Oct 21 '14 at 19:37
  • @DeepSidhu1313 Yes, you can use line numbers in CodeArea, but you have to enable them using `codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea))`. Please, use RichTextFX's [issue tracker](https://github.com/TomasMikula/RichTextFX/issues) for further help. – Tomas Mikula Oct 22 '14 at 17:07
1

The editor sample I posted is not a type and preview method, it's a JavaScript editor embedded (codemirror) into a JavaFX application using WebKit. You can find the related source here or an updated version for a mini-IDE in the conception project.

jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • I am so waiting for `Java 8` to come out stable. `SwingNode` seems like a bliss. I will have all my favorite Swing things in JavaFX =) and oh... does that support indenting ? – An SO User Oct 26 '13 at 06:17
  • Not sure exactly what you refer to by indenting, but the answer is probably yes, it's pretty functional for basic editing tasks. You should just run the code and see for yourself if it could be made to fit your needs. – jewelsea Oct 26 '13 at 06:24
  • It seems I am missing the part on how to add CodeMirror to WebView – An SO User Oct 26 '13 at 13:07
1

I am currently using Ace Editor in my open source project via the WebEngine. Here is the Kitchen Sink demo.

UPDATE

A possible approach to JS/FX interaction as of current JDK version:

  • Write the JS app/widget part, test it standalone. If you only intending to embed an editor widget, then it could be an empty web page with a <div> which is your editor.
  • Then a plan for a 'get text from JS' scenario might be like this: 'call the JS function from Java, it will get the text from the editor element and call back the Java part with text passed as String argument for a method'.
  • Learn the Java-JS binding - i.e. WebView callback from Javascript
  • Embed FirebugLite to debug your JS from the WebView. The only version which worked for me was:

    <script src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'>

Some general advices - try to avoid complexity in JS-to-Java calls. I filed a couple of bugs to the JavaFX team because some simple things like overriding a method didn't work for me. Avoid passing Java objects to JS - though it is theoretically possible, I always ended up with application crashes. So now I am passing JSON and convert it to objects on both sides.

You may have a look at a working example here of an AngularJS/JavaFX application. It's in a pre-alpha state, so it may not even launch on your machine, but can be seen as proof of concept of an AngularJS desktop app.

Community
  • 1
  • 1
Andrey Chaschev
  • 16,160
  • 5
  • 51
  • 68
0

I adapted this code for RichTextFX to create my own self contained TextCodeArea. You should be able to just add this to your application and run with it. You just nee to pass it an AnchorPane node to attach itself to.

public class TextCodeArea {

    private static final String[] KEYWORDS = new String[] {
            "abstract", "assert", "boolean", "break", "byte",
            "case", "catch", "char", "class", "const",
            "continue", "default", "do", "double", "else",
            "enum", "extends", "final", "finally", "float",
            "for", "goto", "if", "implements", "import",
            "instanceof", "int", "interface", "long", "native",
            "new", "package", "private", "protected", "public",
            "return", "short", "static", "strictfp", "super",
            "switch", "synchronized", "this", "throw", "throws",
            "transient", "try", "void", "volatile", "while"
    };

    private static final String KEYWORD_PATTERN = "\\b(" + String.join("|", KEYWORDS) + ")\\b";
    private static final String PAREN_PATTERN = "\\(|\\)";
    private static final String BRACE_PATTERN = "\\{|\\}";
    private static final String BRACKET_PATTERN = "\\[|\\]";
    private static final String SEMICOLON_PATTERN = "\\;";
    private static final String STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\"";
    private static final String COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/";
    private static final String ASSIGNMENT_PATTERN = "\\s+\\w+?\\s+=" + "|" + "\\s+\\w+\\[.*\\]?\\s+=";

    private static final Pattern PATTERN = Pattern.compile(
            "(?<KEYWORD>" + KEYWORD_PATTERN + ")"
                    + "|(?<PAREN>" + PAREN_PATTERN + ")"
                    + "|(?<BRACE>" + BRACE_PATTERN + ")"
                    + "|(?<BRACKET>" + BRACKET_PATTERN + ")"
                    + "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
                    + "|(?<STRING>" + STRING_PATTERN + ")"
                    + "|(?<COMMENT>" + COMMENT_PATTERN + ")"
                    + "|(?<ASSIGNMENT>" + ASSIGNMENT_PATTERN + ")"
    );

    private CodeArea codeArea;

    public  TextCodeArea(AnchorPane pane) {
        codeArea = new CodeArea();

        VirtualizedScrollPane sp = new VirtualizedScrollPane(codeArea);
        pane.getChildren().add(sp);
        AnchorPane.setLeftAnchor(sp, 0.0);
        AnchorPane.setRightAnchor(sp, 0.0);
        AnchorPane.setBottomAnchor(sp, 0.0);
        AnchorPane.setTopAnchor(sp, 0.0);
        codeArea.prefWidthProperty().bind(pane.widthProperty());
        codeArea.prefHeightProperty().bind(pane.heightProperty());
        codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea));
        Subscription cleanupWhenNoLongerNeedIt = codeArea.multiPlainChanges()
                .successionEnds(java.time.Duration.ofMillis(50))
                .subscribe(ignore -> codeArea.setStyleSpans(0, computeHighlighting(codeArea.getText())));
        final Pattern whiteSpace = Pattern.compile( "^\\s+" );
        codeArea.addEventHandler( KeyEvent.KEY_PRESSED, key -> {
            if (key.getCode() == KeyCode.ENTER) {
                int pos = codeArea.getCaretPosition();
                int par = codeArea.getCurrentParagraph();
                Matcher matcher = whiteSpace.matcher(codeArea.getParagraph(par-1).getSegments().get(0));
                if (matcher.find()) Platform.runLater(() -> codeArea.insertText(pos, matcher.group()));
            }
        });
//        cleanupWhenNoLongerNeedIt.unsubscribe();    // to stop and clean up
    }

    private static StyleSpans<Collection<String>> computeHighlighting(String text) {
        int lastKwEnd = 0;
        Matcher matcher = PATTERN.matcher(text);
        StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>();

        while(matcher.find()) {
            String styleClass =
                    matcher.group("KEYWORD") != null ? "keyword" :
                            matcher.group("PAREN") != null ? "paren" :
                                    matcher.group("BRACE") != null ? "brace" :
                                            matcher.group("BRACKET") != null ? "bracket" :
                                                    matcher.group("SEMICOLON") != null ? "semicolon" :
                                                            matcher.group("STRING") != null ? "string" :
                                                                    matcher.group("COMMENT") != null ? "comment" :
                                                                            matcher.group("ASSIGNMENT") != null ? "assignment" :
                                                                                    null; /* never happens */ assert styleClass != null;
            spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd);
            spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start());
            lastKwEnd = matcher.end();
        }
        spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);
        return spansBuilder.create();
    }
}

Also make sure to include your CSS in your JavaFX application Main function:

    scene.getStylesheets().add(getClass().getResource("../java-keywords.css").toExternalForm());
.styled-text-area {
    -fx-font-size: 18;
    -fx-background-color: rgb(0, 27, 51);
}

.styled-text-area .caret {
    -fx-stroke: white;
}

.styled-text-area .text{
    -fx-fill:white;
}

.styled-text-area .line {
    -fx-fill: black;
}

.styled-text-area .text.assignment {
    -fx-fill: orange;
    -fx-font-weight: bold;
}

.styled-text-area .text.keyword {
    -fx-fill: rgb(110, 252, 187);
    -fx-font-weight: bold;
}

.styled-text-area .text.semicolon {
    -fx-fill: rgb(110, 252, 187);
    -fx-font-weight: bold;
}

.styled-text-area .text.paren {
    -fx-fill: yellow;
    -fx-font-weight: bold;
}

.styled-text-area .text.bracket {
    -fx-fill: white;
    -fx-font-weight: bold;
}

.styled-text-area .text.brace {
    -fx-fill: yellow;
    -fx-font-weight: bold;
}

.styled-text-area .text.string {
    -fx-fill: rgb(58,213,11);
}

.styled-text-area .text.comment {
    -fx-fill: rgb(0, 200, 255);
}

.paragraph-box:has-caret{
    -fx-background-color: rgb(50, 77, 101);
}