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);
}