From 5acaa5ee88320f35e25c6805d89e833907e79ace Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 4 Sep 2017 18:32:05 +0300 Subject: [PATCH] Add syntax highlighter --- .../main/java/com/annimon/hotarufx/Main.java | 12 ++++ .../hotarufx/ui/SyntaxHighlighter.java | 63 +++++++++++++++++++ .../ui/controller/EditorController.java | 11 +++- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/annimon/hotarufx/ui/SyntaxHighlighter.java diff --git a/app/src/main/java/com/annimon/hotarufx/Main.java b/app/src/main/java/com/annimon/hotarufx/Main.java index 5ff582e..5b1ab56 100644 --- a/app/src/main/java/com/annimon/hotarufx/Main.java +++ b/app/src/main/java/com/annimon/hotarufx/Main.java @@ -1,5 +1,6 @@ package com.annimon.hotarufx; +import com.annimon.hotarufx.ui.controller.EditorController; import java.io.IOException; import javafx.application.Application; import javafx.fxml.FXMLLoader; @@ -9,12 +10,15 @@ import lombok.val; public class Main extends Application { + private EditorController controller; + @Override public void start(Stage primaryStage) { primaryStage.setTitle("HotaruFX"); try { val loader = new FXMLLoader(getClass().getResource("/fxml/Editor.fxml")); val scene = new Scene(loader.load()); + controller = loader.getController(); primaryStage.setScene(scene); } catch (IOException e) { // TODO: notice me!! @@ -22,6 +26,14 @@ public class Main extends Application { primaryStage.show(); } + @Override + public void stop() throws Exception { + if (controller != null) { + controller.stop(); + } + super.stop(); + } + public static void main(String[] args) { launch(args); } diff --git a/app/src/main/java/com/annimon/hotarufx/ui/SyntaxHighlighter.java b/app/src/main/java/com/annimon/hotarufx/ui/SyntaxHighlighter.java new file mode 100644 index 0000000..303f038 --- /dev/null +++ b/app/src/main/java/com/annimon/hotarufx/ui/SyntaxHighlighter.java @@ -0,0 +1,63 @@ +package com.annimon.hotarufx.ui; + +import com.annimon.hotarufx.lexer.HotaruLexer; +import java.time.Duration; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import javafx.concurrent.Task; +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.fxmisc.richtext.CodeArea; +import org.fxmisc.richtext.StyleSpans; +import org.fxmisc.richtext.StyleSpansBuilder; + +@RequiredArgsConstructor +public class SyntaxHighlighter { + + private final CodeArea editor; + private final ExecutorService executor; + + public void init() { + editor.richChanges() + .filter(ch -> !ch.getInserted().equals(ch.getRemoved())) + .successionEnds(Duration.ofMillis(500)) + .supplyTask(this::computeHighlightingAsync) + .awaitLatest(editor.richChanges()) + .filterMap(t -> Optional.ofNullable(t.isSuccess() ? t.get() : null)) + .subscribe(spans -> editor.setStyleSpans(0, spans)); + } + + public void release() { + executor.shutdown(); + } + + private Task>> computeHighlightingAsync() { + val text = editor.getText(); + val task = new Task>>() { + @Override + protected StyleSpans> call() throws Exception { + val spans = new StyleSpansBuilder>(); + for (val t : new HotaruLexer(text).tokenize()) { + val category = t.getType().getPrimaryCategory(); + switch (category) { + case "string": + case "keyword": + case "comment": + case "number": + spans.add(Collections.singleton(category), t.getLength()); + break; + + default: + spans.add(Collections.emptyList(), t.getLength()); + break; + } + } + return spans.create(); + } + }; + executor.execute(task); + return task; + } +} diff --git a/app/src/main/java/com/annimon/hotarufx/ui/controller/EditorController.java b/app/src/main/java/com/annimon/hotarufx/ui/controller/EditorController.java index 954ecc2..e52fda4 100644 --- a/app/src/main/java/com/annimon/hotarufx/ui/controller/EditorController.java +++ b/app/src/main/java/com/annimon/hotarufx/ui/controller/EditorController.java @@ -9,13 +9,14 @@ import com.annimon.hotarufx.lib.Context; import com.annimon.hotarufx.parser.HotaruParser; import com.annimon.hotarufx.parser.ParseError; import com.annimon.hotarufx.parser.visitors.InterpreterVisitor; +import com.annimon.hotarufx.ui.SyntaxHighlighter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.time.Duration; import java.util.Arrays; import java.util.ResourceBundle; +import java.util.concurrent.Executors; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -37,6 +38,8 @@ public class EditorController implements Initializable { @FXML private TitledPane logPane; + private SyntaxHighlighter syntaxHighlighter; + @FXML private void handleMenuExit(ActionEvent event) { // TODO: confirmation @@ -81,9 +84,15 @@ public class EditorController implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { editor.setParagraphGraphicFactory(LineNumberFactory.get(editor)); + syntaxHighlighter = new SyntaxHighlighter(editor, Executors.newSingleThreadExecutor()); + syntaxHighlighter.init(); editor.replaceText(0, 0, readProgram("/main.hfx")); } + public void stop() { + syntaxHighlighter.release(); + } + private static String readProgram(String path) { val fallbackProgram = "composition(640, 480, 25)"; try (InputStream is = Main.class.getResourceAsStream(path)) {