Add syntax highlighting framework

This commit is contained in:
Oystein Kristoffer Tveit 2021-02-22 15:17:31 +01:00
parent f94612cf87
commit 1860d4c57f
4 changed files with 133 additions and 2 deletions

View File

@ -1,17 +1,20 @@
package app.controllers; package app.controllers;
import java.net.URL; import java.net.URL;
import java.util.Collection;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory; import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.model.StyleSpans;
import org.fxmisc.richtext.model.TwoDimensional.Bias; import org.fxmisc.richtext.model.TwoDimensional.Bias;
import org.fxmisc.richtext.model.TwoDimensional.Position; import org.fxmisc.richtext.model.TwoDimensional.Position;
import app.events.EditorChangedEvent; import app.events.EditorChangedEvent;
import app.model.Model; import app.model.Model;
import app.service.LanguageOperations;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
@ -43,6 +46,10 @@ public class EditorController implements Initializable, Controller {
this.model = model; this.model = model;
} }
public void setHighlighting(StyleSpans<Collection<String>> highlighting) {
this.editor.setStyleSpans(0, highlighting);
}
/** /**
* Handles events from the editor, and reflects them in the model * Handles events from the editor, and reflects them in the model
* @param event The object containing metadata of the event * @param event The object containing metadata of the event
@ -57,6 +64,10 @@ public class EditorController implements Initializable, Controller {
pos.getMajor() + 1, pos.getMajor() + 1,
pos.getMinor() pos.getMinor()
)); ));
this.setHighlighting(
LanguageOperations.syntaxHighlight(
this.editor.getText(),
Model.getLanguage()));
} }
} }

View File

@ -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<Pattern, String> e(String k, String v) {
return new AbstractMap.SimpleEntry<>(Pattern.compile(k), v);
}
private static final Map<Pattern, String> pattern =
Map.ofEntries(
e("\\b(" + String.join("|", keywords) + ")\\b", "keyword"), // <public>, <static>, ....
e("this", "this"), // <this>
e("true", "true"), // <true>
e("false", "false"), // <false>
e("\\(|\\)", "paranthesis"), // <(*)>
e("\\{|\\}", "curlyBrackets"), // <{*}>
e("\\[|\\]", "squareBrackets"), // <[*]>
e(";", "semiColon"), // <;>
e("[A-Z]\\w+", "identifier"), // <ClassName> or <InterfaceName>
e("\\.\\w+", "property"), // <.property>
e("(?<=this\\.)?\\w+(?=\\(*\\))", "method") // <this.function(*)>
);
public String getName() {
return this.name;
}
public URL getIcon() {
return this.iconPath;
}
public Map<Pattern, String> 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("|")));
}
}

View File

@ -1,7 +1,5 @@
package app.model; package app.model;
import app.languages.Java;
/** /**
* Data model of the application * Data model of the application
*/ */

View File

@ -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<String> kws) {
return kws
.stream()
.filter(keyword -> m.group(keyword) != null)
.findFirst()
.orElse(""); // This is not possible, but is needed to convert Optional<String> to String
}
public static StyleSpans<Collection<String>>
syntaxHighlight(String text, ProgrammingLanguage language) {
Matcher matcher = language.getPattern().matcher(text);
StyleSpansBuilder<Collection<String>> 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();
}
}