Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.fxmisc.richtext.demo;

import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Matcher;
Expand All @@ -15,6 +16,7 @@
import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.model.StyleSpans;
import org.fxmisc.richtext.model.StyleSpansBuilder;
import org.reactfx.Subscription;

public class JavaKeywords extends Application {

Expand Down Expand Up @@ -80,13 +82,27 @@ public static void main(String[] args) {
@Override
public void start(Stage primaryStage) {
CodeArea codeArea = new CodeArea();

// add line numbers to the left of area
codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea));

codeArea.richChanges()
.filter(ch -> !ch.getInserted().equals(ch.getRemoved())) // XXX
.subscribe(change -> {
codeArea.setStyleSpans(0, computeHighlighting(codeArea.getText()));
});
// recompute the syntax highlighting 500 ms after user stops editing area
Subscription cleanupWhenNoLongerNeedIt = codeArea

// plain changes = ignore style changes that are emitted when syntax highlighting is reapplied
// multi plain changes = save computation by not rerunning the code multiple times
// when making multiple changes (e.g. renaming a method at multiple parts in file)
.multiPlainChanges()

// do not emit an event until 500 ms have passed since the last emission of previous stream
.successionEnds(Duration.ofMillis(500))

// run the following code block when previous stream emits an event
.subscribe(ignore -> codeArea.setStyleSpans(0, computeHighlighting(codeArea.getText())));

// when no longer need syntax highlighting and wish to clean up memory leaks
// run: `cleanupWhenNoLongerNeedIt.unsubscribe();`

codeArea.replaceText(0, 0, sampleCode);

Scene scene = new Scene(new StackPane(new VirtualizedScrollPane<>(codeArea)), 600, 400);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.model.StyleSpans;
import org.fxmisc.richtext.model.StyleSpansBuilder;
import org.reactfx.Subscription;

public class JavaKeywordsAsync extends Application {

Expand Down Expand Up @@ -90,11 +91,10 @@ public void start(Stage primaryStage) {
executor = Executors.newSingleThreadExecutor();
codeArea = new CodeArea();
codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea));
codeArea.richChanges()
.filter(ch -> !ch.getInserted().equals(ch.getRemoved())) // XXX
Subscription cleanupWhenDone = codeArea.multiPlainChanges()
.successionEnds(Duration.ofMillis(500))
.supplyTask(this::computeHighlightingAsync)
.awaitLatest(codeArea.richChanges())
.awaitLatest(codeArea.multiPlainChanges())
.filterMap(t -> {
if(t.isSuccess()) {
return Optional.of(t.get());
Expand All @@ -104,6 +104,9 @@ public void start(Stage primaryStage) {
}
})
.subscribe(this::applyHighlighting);

// call when no longer need it: `cleanupWhenFinished.unsubscribe();`

codeArea.replaceText(0, 0, sampleCode);

Scene scene = new Scene(new StackPane(new VirtualizedScrollPane<>(codeArea)), 600, 400);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.BreakIterator;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
Expand All @@ -19,6 +20,7 @@
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.reactfx.Subscription;

public class SpellChecking extends Application {

Expand All @@ -33,12 +35,14 @@ public void start(Stage primaryStage) {
StyleClassedTextArea textArea = new StyleClassedTextArea();
textArea.setWrapText(true);

textArea.richChanges()
.filter(ch -> !ch.getInserted().equals(ch.getRemoved())) // XXX
Subscription cleanupWhenFinished = textArea.multiPlainChanges()
.successionEnds(Duration.ofMillis(500))
.subscribe(change -> {
textArea.setStyleSpans(0, computeHighlighting(textArea.getText()));
});

// call when no longer need it: `cleanupWhenFinished.unsubscribe();`

// load the dictionary
try (InputStream input = getClass().getResourceAsStream("spellchecking.dict");
BufferedReader br = new BufferedReader(new InputStreamReader(input))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,14 @@ public interface EditableStyledDocument<PS, SEG, S> extends StyledDocument<PS, S
*/
default EventStream<List<PlainTextChange>> multiPlainChanges() {
return multiRichChanges()
// map to a List<PlainTextChange>
.map(list -> Arrays.asList(list.stream()
.map(RichTextChange::toPlainTextChange)
// filter out rich changes where the style was changed but text wasn't added/removed
.filter(pc -> !pc.isIdentity())
.toArray(PlainTextChange[]::new)));
.filter(rtc -> !rtc.isPlainTextIdentity())
.map(RichTextChange::toPlainTextChange)
.toArray(PlainTextChange[]::new)))
// only emit non-empty lists
.filter(list -> !list.isEmpty());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,12 @@ protected final RichTextChange<PS, SEG, S> create(int position, StyledDocument<P
public final PlainTextChange toPlainTextChange() {
return new PlainTextChange(position, removed.getText(), inserted.getText());
}

/**
* Equivalent to {@code richChange.toPlainTextChange().isIdentity()} but without the additional object
* creation via {@link #toPlainTextChange()}.
*/
public final boolean isPlainTextIdentity() {
return removed.getText().equals(inserted.getText());
}
}