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<StyleSpans<Collection<String>>> computeHighlightingAsync() {
        val text = editor.getText();
        val task = new Task<StyleSpans<Collection<String>>>() {
            @Override
            protected StyleSpans<Collection<String>> call() throws Exception {
                val spans = new StyleSpansBuilder<Collection<String>>();
                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;
    }
}

@@ -27,16 +27,116 @@
 fx.ui;%0A%0A
+import com.annimon.hotarufx.bundles.BundleLoader;%0Aimport com.annimon.hotarufx.bundles.FunctionType;%0A
 import c
@@ -256,24 +256,46 @@
 ollections;%0A
+import java.util.Map;%0A
 import java.
@@ -305,24 +305,46 @@
 l.Optional;%0A
+import java.util.Set;%0A
 import java.
@@ -376,16 +376,52 @@
 ervice;%0A
+import java.util.stream.Collectors;%0A
 import j
@@ -766,33 +766,291 @@
 or;%0A
-%0A    public void init() %7B
+    private Set%3CString%3E nodeFunctions;%0A%0A    public void init() %7B%0A        nodeFunctions = BundleLoader.functions().entrySet().stream()%0A                .filter(e -%3E e.getValue() == FunctionType.NODE)%0A                .map(Map.Entry::getKey)%0A                .collect(Collectors.toSet());
 %0A   
@@ -2348,16 +2348,414 @@
 break;%0A%0A
+                        case %22identifier%22:%0A                            if (nodeFunctions.contains(t.getText())) %7B%0A                                spans.add(Collections.singleton(%22node-function%22), t.getLength());%0A                            %7D else %7B%0A                                spans.add(Collections.emptyList(), t.getLength());%0A                            %7D%0A                            break;%0A%0A
         

Add syntax highlight for node functions