What is JavaFX equivalent of JSyntaxPane for making a code editor?
Asked Answered
R

4

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?

Recently answered 26/10, 2013 at 6:0 Comment(0)
M
2

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.

Montherlant answered 26/10, 2013 at 8:2 Comment(11)
Can you tell me how to work with any javascript library in javafx ? I mean the generalized steps. I am beginning to like Ace EditorRecently
Thanks. I will get back to you as and when I need help =) So basically, the trick is to create a webpage and then embed it into the WebView if I am not mistakenRecently
Yes. I have skipped this 'very first' part =)Montherlant
Do you maintain a blog ? If so, you can post step-wise instructions to this. Really needed =))Recently
I'm thinking of it, but I don't have time for it at the moment.Montherlant
oops... looks like will have to do some late-night research haha ! ;-)Recently
You've talked me into this. I'll try creating my first post on this topic! Perhaps this could attract some traffic to the Bear.Montherlant
Started a blog and will add a post today. Be the first guest! :-)Montherlant
Saw the blog ! Link your SO to your blog. ;-) Eagerly waiting for the code editor post.Recently
#19619626Recently
let us continue this discussion in chatRecently
C
14

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

Note that it requires JDK8.

Constellate answered 1/2, 2014 at 16:17 Comment(2)
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. :)Adjudication
@Adjudication 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 for further help.Constellate
M
2

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.

Montherlant answered 26/10, 2013 at 8:2 Comment(11)
Can you tell me how to work with any javascript library in javafx ? I mean the generalized steps. I am beginning to like Ace EditorRecently
Thanks. I will get back to you as and when I need help =) So basically, the trick is to create a webpage and then embed it into the WebView if I am not mistakenRecently
Yes. I have skipped this 'very first' part =)Montherlant
Do you maintain a blog ? If so, you can post step-wise instructions to this. Really needed =))Recently
I'm thinking of it, but I don't have time for it at the moment.Montherlant
oops... looks like will have to do some late-night research haha ! ;-)Recently
You've talked me into this. I'll try creating my first post on this topic! Perhaps this could attract some traffic to the Bear.Montherlant
Started a blog and will add a post today. Be the first guest! :-)Montherlant
Saw the blog ! Link your SO to your blog. ;-) Eagerly waiting for the code editor post.Recently
#19619626Recently
let us continue this discussion in chatRecently
H
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.

Holoenzyme answered 26/10, 2013 at 6:15 Comment(3)
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 ?Recently
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.Holoenzyme
It seems I am missing the part on how to add CodeMirror to WebViewRecently
I
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);
}
Iridotomy answered 6/11, 2020 at 12:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.