/*
 * Copyright (c) 2013, 2014, Tomas Mikula. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package org.fxmisc.richtext.demo;

import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.PlainTextChange;
import org.fxmisc.richtext.StyleSpans;
import org.fxmisc.richtext.StyleSpansBuilder;
import org.reactfx.EventStream;

public class JavaKeywordsAsync extends Application {

    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", "this", "throw", "throws",
            "transient", "try", "void", "volatile", "while"
    };

    private static final Pattern KEYWORD_PATTERN = Pattern.compile("\\b(" + String.join("|", KEYWORDS) + ")\\b");

    private static final String sampleCode = String.join("\n", new String[] {
        "package com.example;",
        "",
        "import java.util.*;",
        "",
        "public class Foo extends Bar implements Baz {",
        "",
        "   public static void main(String[] args) {",
        "       for(String arg: args) {",
        "           if(arg.length() != 0)",
        "               System.out.println(arg);",
        "           else",
        "               System.err.println(\"Warning: empty string as argument\");",
        "       }",
        "   }",
        "",
        "}"
    });


    public static void main(String[] args) {
        launch(args);
    }

    private ExecutorService executor;

    @Override
    public void start(Stage primaryStage) {
        executor = Executors.newSingleThreadExecutor();
        CodeArea codeArea = new CodeArea();
        EventStream<PlainTextChange> textChanges = codeArea.plainTextChanges();
        textChanges
                .reduceCloseSuccessions((a, b) -> b, Duration.ofMillis(500))
                .mapToTask(ch -> computeHighlightingAsync(codeArea.getText()))
                .awaitLatest(textChanges)
                .subscribe(highlighting -> {
                    codeArea.setStyleSpans(0, highlighting);
                });
        codeArea.replaceText(0, 0, sampleCode);

        Scene scene = new Scene(new StackPane(codeArea), 600, 400);
        scene.getStylesheets().add(JavaKeywordsAsync.class.getResource("java-keywords.css").toExternalForm());
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    @Override
    public void stop() {
        executor.shutdown();
    }

    private Task<StyleSpans<Collection<String>>> computeHighlightingAsync(String text) {
        Task<StyleSpans<Collection<String>>> task = new Task<StyleSpans<Collection<String>>>() {
            @Override
            protected StyleSpans<Collection<String>> call() throws Exception {
                return computeHighlighting(text);
            }
        };
        executor.execute(task);
        return task;
    }

    private static StyleSpans<Collection<String>> computeHighlighting(String text) {
        Matcher matcher = KEYWORD_PATTERN.matcher(text);
        int lastKwEnd = 0;
        StyleSpansBuilder<Collection<String>> spansBuilder
                = new StyleSpansBuilder<>();
        while(matcher.find()) {
            spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd);
            spansBuilder.add(Collections.singleton("keyword"), matcher.end() - matcher.start());
            lastKwEnd = matcher.end();
        }
        spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);
        return spansBuilder.create();
    }
}

@@ -3342,32 +3342,63 @@
 h(args);%0A    %7D%0A%0A
+    private CodeArea codeArea;%0A
     private Exec
@@ -3542,25 +3542,16 @@
         
-CodeArea 
 codeArea
@@ -3690,44 +3690,23 @@
    .
-reduceCloseSuccessions((a, b) -%3E b, 
+successionEnds(
 Dura
@@ -3746,24 +3746,25 @@
    .
-mapToTask(ch -%3E 
+supplyTask(this::
 comp
@@ -3787,28 +3787,8 @@
 sync
-(codeArea.getText())
 )%0A  
@@ -3854,18 +3854,29 @@
 bscribe(
+t
 hi
+s::applyHi
 ghlighti
@@ -3881,92 +3881,8 @@
 ting
- -%3E %7B%0A                    codeArea.setStyleSpans(0, highlighting);%0A                %7D
 );%0A 
@@ -4331,30 +4331,61 @@
 ngAsync(
-String text) %7B
+) %7B%0A        String text = codeArea.getText();
 %0A       
@@ -4702,32 +4702,170 @@
 rn task;%0A    %7D%0A%0A
+    private void applyHighlighting(StyleSpans%3CCollection%3CString%3E%3E highlighting) %7B%0A        codeArea.setStyleSpans(0, highlighting);%0A    %7D%0A%0A
     private stat

Update JavaKeywordsAsync demo.