diff --git a/src/main/java/app/controllers/EditorController.java b/src/main/java/app/controllers/EditorController.java index 323dccb..1a4e542 100644 --- a/src/main/java/app/controllers/EditorController.java +++ b/src/main/java/app/controllers/EditorController.java @@ -1,17 +1,20 @@ package app.controllers; import java.net.URL; +import java.util.Collection; import java.util.ResourceBundle; import com.google.common.eventbus.EventBus; import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.LineNumberFactory; +import org.fxmisc.richtext.model.StyleSpans; import org.fxmisc.richtext.model.TwoDimensional.Bias; import org.fxmisc.richtext.model.TwoDimensional.Position; import app.events.EditorChangedEvent; import app.model.Model; +import app.service.LanguageOperations; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.input.KeyEvent; @@ -43,6 +46,10 @@ public class EditorController implements Initializable, Controller { this.model = model; } + public void setHighlighting(StyleSpans> highlighting) { + this.editor.setStyleSpans(0, highlighting); + } + /** * Handles events from the editor, and reflects them in the model * @param event The object containing metadata of the event @@ -57,6 +64,10 @@ public class EditorController implements Initializable, Controller { pos.getMajor() + 1, pos.getMinor() )); + this.setHighlighting( + LanguageOperations.syntaxHighlight( + this.editor.getText(), + Model.getLanguage())); } } diff --git a/src/main/java/app/languages/Java.java b/src/main/java/app/languages/Java.java new file mode 100644 index 0000000..64bcce0 --- /dev/null +++ b/src/main/java/app/languages/Java.java @@ -0,0 +1,71 @@ +package app.languages; + +import java.net.URL; +import java.util.AbstractMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import app.model.ProgrammingLanguage; + +public class Java implements ProgrammingLanguage { + + private String name = "Java"; + private URL iconPath = this.getClass().getResource(""); + + 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", "throw", "throws", + "transient", "try", "void", "volatile", "while" + }; + + private static Entry e(String k, String v) { + return new AbstractMap.SimpleEntry<>(Pattern.compile(k), v); + } + + private static final Map pattern = + Map.ofEntries( + e("\\b(" + String.join("|", keywords) + ")\\b", "keyword"), // , , .... + e("this", "this"), // + e("true", "true"), // + e("false", "false"), // + e("\\(|\\)", "paranthesis"), // <(*)> + e("\\{|\\}", "curlyBrackets"), // <{*}> + e("\\[|\\]", "squareBrackets"), // <[*]> + e(";", "semiColon"), // <;> + e("[A-Z]\\w+", "identifier"), // or + e("\\.\\w+", "property"), // <.property> + e("(?<=this\\.)?\\w+(?=\\(*\\))", "method") // + ); + + public String getName() { + return this.name; + } + + public URL getIcon() { + return this.iconPath; + } + + public Map getPatternMap() { + return Java.pattern; + } + + // https://stackoverflow.com/questions/16517689/java-regex-capturing-groups-indexes/16582083 + + public Pattern getPattern() { + return Pattern.compile( + Java.pattern + .entrySet() + .stream() + .map(e -> String.format("(?<%s>%s)", e.getValue(), e.getKey())) + .collect(Collectors.joining("|"))); + } +} diff --git a/src/main/java/app/model/Model.java b/src/main/java/app/model/Model.java index 7c65ff4..298c6d0 100644 --- a/src/main/java/app/model/Model.java +++ b/src/main/java/app/model/Model.java @@ -1,7 +1,5 @@ package app.model; -import app.languages.Java; - /** * Data model of the application */ diff --git a/src/main/java/app/service/LanguageOperations.java b/src/main/java/app/service/LanguageOperations.java new file mode 100644 index 0000000..10942b4 --- /dev/null +++ b/src/main/java/app/service/LanguageOperations.java @@ -0,0 +1,51 @@ +package app.service; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; + +import org.fxmisc.richtext.model.StyleSpans; +import org.fxmisc.richtext.model.StyleSpansBuilder; + +import app.model.ProgrammingLanguage; + +public class LanguageOperations { + + private static String getMatch(Matcher m, Collection kws) { + return kws + .stream() + .filter(keyword -> m.group(keyword) != null) + .findFirst() + .orElse(""); // This is not possible, but is needed to convert Optional to String + } + + public static StyleSpans> + syntaxHighlight(String text, ProgrammingLanguage language) { + Matcher matcher = language.getPattern().matcher(text); + StyleSpansBuilder> spansBuilder = new StyleSpansBuilder<>(); + + int lastKwEnd = 0; + while(matcher.find()) { + String styleClass = getMatch(matcher, language.getPatternMap().values()); + + 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(); + } + +}