/*
* Nocturne
* Copyright (c) 2015-2016, Lapis <https://github.com/LapisBlue>
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package blue.lapis.nocturne.util;
import blue.lapis.nocturne.gui.scene.text.SelectableMember;
import javafx.scene.Node;
import javafx.scene.text.Text;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Static utility class for handling syntax highlighting of Java code.
*/
public final class JavaSyntaxHighlighter {
private JavaSyntaxHighlighter() {
}
private static final String[] KEYWORDS = new String[] {
"abstract", "assert", "boolean", "break", "byte",
"case", "catch", "char", "class", "const",
"continue", "default", "do", "double", "else",
"enum", "extends", "false", "final", "finally",
"float", "for", "goto", "if", "implements",
"import", "instanceof", "int", "interface", "long",
"native", "new", "null", "package", "private",
"protected", "public", "return", "short", "static",
"strictfp", "super", "switch", "synchronized", "this",
"throw", "throws", "transient", "true", "try",
"void", "volatile", "while"
};
private static final String KEYWORD_PATTERN = "\\b(" + String.join("|", KEYWORDS) + ")\\b";
private static final String SEMICOLON_PATTERN = ";";
private static final String STRING_PATTERN = "(\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)')";
private static final String NUMBER_PATTERN = "[^\\w]((?:\\d+(?:\\.\\d+)?)+[DdFfLl]?)";
private static final String[] PATTERN_NAMES = {"KEYWORD", "SEMICOLON", "STRING", "NUMBER"};
private static final Pattern PATTERN = Pattern.compile(
"(?<KEYWORD>" + KEYWORD_PATTERN + ")"
+ "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")"
+ "|(?<STRING>" + STRING_PATTERN + ")"
+ "|(?<NUMBER>" + NUMBER_PATTERN + ")"
);
/**
* Applies syntax highlighting to the given {@link Node} list.
*
* <p><em>Note: This method is atomic. As such, if an exception occurs while
* processing the nodes, the list will remain unmodified.</em></p>
*
* @param nodes The {@link Node} list to apply highlighting to
*/
public static void highlight(List<Node> nodes) {
List<Node> newNodes = new ArrayList<>();
nodes.forEach(node -> {
if (node.getClass() == SelectableMember.class) {
newNodes.add(node);
return;
}
String text = ((Text) node).getText();
Matcher matcher = PATTERN.matcher(text);
int lastIndex = 0;
while (matcher.find()) {
String group = null;
for (String pattern : PATTERN_NAMES) {
if (matcher.group(pattern) != null) {
group = pattern;
break;
}
}
assert group != null;
int start = matcher.start(group);
int end = matcher.end(group);
if (group.equals("NUMBER") && !Character.isDigit(matcher.group(group).charAt(0))) {
//TODO: I am a horrible person
start += 1;
}
newNodes.add(new Text(text.substring(lastIndex, start)));
Text syntaxItem = new Text(text.substring(start, end));
syntaxItem.getStyleClass().add("syntax");
syntaxItem.getStyleClass().add(group.toLowerCase());
newNodes.add(syntaxItem);
lastIndex = matcher.end();
}
newNodes.add(new Text(text.substring(lastIndex)));
});
nodes.clear();
nodes.addAll(newNodes);
}
}