<commit_before>package org.fxmisc.richtext;

import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.CssMetaData;
import javafx.css.StyleConverter;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.geometry.Bounds;
import javafx.scene.shape.Path;
import org.fxmisc.richtext.model.PlainTextChange;
import org.fxmisc.richtext.model.TwoDimensional;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.StateMachine;
import org.reactfx.Subscription;
import org.reactfx.Suspendable;
import org.reactfx.SuspendableNo;
import org.reactfx.value.SuspendableVal;
import org.reactfx.value.Val;
import org.reactfx.value.Var;

import java.text.BreakIterator;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Consumer;

import static javafx.util.Duration.ZERO;
import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward;
import static org.reactfx.EventStreams.invalidationsOf;
import static org.reactfx.EventStreams.merge;

/**
 * Default implementation for a {@link Caret}. Since only one {@link Path} object is used per caret, the model
 * and view were combined into one item to grant easier access to and modification of CSS-related
 * properties. Caution must be exercised when depending on Path-related properties in any way (e.g.
 * {@link #boundsInLocalProperty()}, {@link #parentProperty()}, etc.). Also, {@link #caretBoundsProperty()}
 * is distinguishable from {@link #boundsInLocalProperty()}.
 *
 * <p>
 *     This class adds the css property "-rtfx-blink-rate" ({@link #blinkRateProperty()}}
 * </p>
 */
public class CaretNode extends Path implements Caret, Comparable<CaretNode> {

    private static final javafx.util.Duration HALF_A_SECOND = javafx.util.Duration.millis(500);

    private static final EventStream<Boolean> ALWAYS_FALSE = Val.constant(false).values();
    private static final EventStream<Boolean> ALWAYS_TRUE = Val.constant(true).values();

    /* ********************************************************************** *
     *                                                                        *
     * Observables                                                            *
     *                                                                        *
     * Observables are "dynamic" (i.e. changing) characteristics of this      *
     * control. They are not directly settable by the client code, but change *
     * in response to user input and/or API actions.                          *
     *                                                                        *
     * ********************************************************************** */

    /**
     * Controls the blink rate of the caret, when one is displayed. Setting
     * the duration to zero disables blinking.
     */
    private final StyleableObjectProperty<javafx.util.Duration> blinkRate
            = new CustomStyleableProperty<>(HALF_A_SECOND, "blinkRate", this, BLINK_RATE);

    /**
     * The blink rate of the caret.
     *
     * Can be styled from CSS using the "-rtfx-blink-rate" property.
     */
    @Override public ObjectProperty<javafx.util.Duration> blinkRateProperty() { return blinkRate; }
    @Override public javafx.util.Duration getBlinkRate() { return blinkRate.getValue(); }
    @Override public void setBlinkRate(javafx.util.Duration rate) { blinkRate.set(rate); }

    private final SuspendableVal<Integer> position;
    @Override public final int getPosition() { return position.getValue(); }
    @Override public final ObservableValue<Integer> positionProperty() { return position; }

    private final SuspendableVal<Integer> paragraphIndex;
    @Override public final int getParagraphIndex() { return paragraphIndex.getValue(); }
    @Override public final ObservableValue<Integer> paragraphIndexProperty() { return paragraphIndex; }

    private final SuspendableVal<OptionalInt> lineIndex;
    @Override public final OptionalInt getLineIndex() { return lineIndex.getValue(); }
    @Override public final ObservableValue<OptionalInt> lineIndexProperty() { return lineIndex; }

    private final SuspendableVal<Integer> columnPosition;
    @Override public final int getColumnPosition() { return columnPosition.getValue(); }
    @Override public final ObservableValue<Integer> columnPositionProperty() { return columnPosition; }

    private final Var<CaretVisibility> showCaret = Var.newSimpleVar(CaretVisibility.AUTO);
    @Override public final CaretVisibility getShowCaret() { return showCaret.getValue(); }
    @Override public final void setShowCaret(CaretVisibility value) { showCaret.setValue(value); }
    @Override public final Var<CaretVisibility> showCaretProperty() { return showCaret; }

    private final SuspendableVal<Optional<Bounds>> bounds;
    @Override public final Optional<Bounds> getCaretBounds() { return bounds.getValue(); }
    @Override public final ObservableValue<Optional<Bounds>> caretBoundsProperty() { return bounds; }

    private Optional<ParagraphBox.CaretOffsetX> targetOffset = Optional.empty();
    @Override public final void clearTargetOffset() { targetOffset = Optional.empty(); }
    @Override public final ParagraphBox.CaretOffsetX getTargetOffset() {
        if (!targetOffset.isPresent()) {
            targetOffset = Optional.of(area.getCaretOffsetX(this));
        }
        return targetOffset.get();
    }

    private final SuspendableNo beingUpdated = new SuspendableNo();
    @Override public final boolean isBeingUpdated() { return beingUpdated.get(); }
    @Override public final SuspendableNo beingUpdatedProperty() { return beingUpdated; }

    private final GenericStyledArea<?, ?, ?> area;
    @Override public GenericStyledArea<?, ?, ?> getArea() { return area; }

    private final String name;
    @Override public final String getCaretName() { return name; }

    private final SuspendableNo dependentBeingUpdated;
    private final EventStream<?> dirty;
    private final Var<Integer> internalTextPosition;

    private Subscription subscriptions = () -> {};

    public CaretNode(String name, GenericStyledArea<?, ?, ?> area) {
        this(name, area, 0);
    }

    public CaretNode(String name, GenericStyledArea<?, ?, ?> area, int startingPosition) {
        this(name, area, area.beingUpdatedProperty(), startingPosition);
    }

    public CaretNode(String name, GenericStyledArea<?, ?, ?> area, SuspendableNo dependentBeingUpdated, int startingPosition) {
        this.name = name;
        this.area = area;
        this.dependentBeingUpdated = dependentBeingUpdated;

        this.getStyleClass().add("caret");
        this.setManaged(false);

        internalTextPosition = Var.newSimpleVar(startingPosition);
        position = internalTextPosition.suspendable();

        Val<TwoDimensional.Position> caretPosition2D = Val.create(
                () -> area.offsetToPosition(internalTextPosition.getValue(), Forward),
                internalTextPosition, area.getParagraphs()
        );
        paragraphIndex = caretPosition2D.map(TwoDimensional.Position::getMajor).suspendable();
        columnPosition = caretPosition2D.map(TwoDimensional.Position::getMinor).suspendable();

        // when content is updated by an area, update the caret of all the other
        // clones that also display the same document
        manageSubscription(area.multiPlainChanges(), list -> {
            int finalPosition = getPosition();
            for (PlainTextChange plainTextChange : list) {
                int netLength = plainTextChange.getNetLength();
                if (netLength != 0) {
                    int indexOfChange = plainTextChange.getPosition();
                    // in case of a replacement: "hello there" -> "hi."
                    int endOfChange = indexOfChange + Math.abs(netLength);

                    /*<commit_msg>Make caret transparent to mouse picking (issue 124) (#1032)<commit_after>package org.fxmisc.richtext;

import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.CssMetaData;
import javafx.css.StyleConverter;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.geometry.Bounds;
import javafx.scene.shape.Path;
import org.fxmisc.richtext.model.PlainTextChange;
import org.fxmisc.richtext.model.TwoDimensional;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.StateMachine;
import org.reactfx.Subscription;
import org.reactfx.Suspendable;
import org.reactfx.SuspendableNo;
import org.reactfx.value.SuspendableVal;
import org.reactfx.value.Val;
import org.reactfx.value.Var;

import java.text.BreakIterator;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Consumer;

import static javafx.util.Duration.ZERO;
import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward;
import static org.reactfx.EventStreams.invalidationsOf;
import static org.reactfx.EventStreams.merge;

/**
 * Default implementation for a {@link Caret}. Since only one {@link Path} object is used per caret, the model
 * and view were combined into one item to grant easier access to and modification of CSS-related
 * properties. Caution must be exercised when depending on Path-related properties in any way (e.g.
 * {@link #boundsInLocalProperty()}, {@link #parentProperty()}, etc.). Also, {@link #caretBoundsProperty()}
 * is distinguishable from {@link #boundsInLocalProperty()}.
 *
 * <p>
 *     This class adds the css property "-rtfx-blink-rate" ({@link #blinkRateProperty()}}
 * </p>
 */
public class CaretNode extends Path implements Caret, Comparable<CaretNode> {

    private static final javafx.util.Duration HALF_A_SECOND = javafx.util.Duration.millis(500);

    private static final EventStream<Boolean> ALWAYS_FALSE = Val.constant(false).values();
    private static final EventStream<Boolean> ALWAYS_TRUE = Val.constant(true).values();

    /* ********************************************************************** *
     *                                                                        *
     * Observables                                                            *
     *                                                                        *
     * Observables are "dynamic" (i.e. changing) characteristics of this      *
     * control. They are not directly settable by the client code, but change *
     * in response to user input and/or API actions.                          *
     *                                                                        *
     * ********************************************************************** */

    /**
     * Controls the blink rate of the caret, when one is displayed. Setting
     * the duration to zero disables blinking.
     */
    private final StyleableObjectProperty<javafx.util.Duration> blinkRate
            = new CustomStyleableProperty<>(HALF_A_SECOND, "blinkRate", this, BLINK_RATE);

    /**
     * The blink rate of the caret.
     *
     * Can be styled from CSS using the "-rtfx-blink-rate" property.
     */
    @Override public ObjectProperty<javafx.util.Duration> blinkRateProperty() { return blinkRate; }
    @Override public javafx.util.Duration getBlinkRate() { return blinkRate.getValue(); }
    @Override public void setBlinkRate(javafx.util.Duration rate) { blinkRate.set(rate); }

    private final SuspendableVal<Integer> position;
    @Override public final int getPosition() { return position.getValue(); }
    @Override public final ObservableValue<Integer> positionProperty() { return position; }

    private final SuspendableVal<Integer> paragraphIndex;
    @Override public final int getParagraphIndex() { return paragraphIndex.getValue(); }
    @Override public final ObservableValue<Integer> paragraphIndexProperty() { return paragraphIndex; }

    private final SuspendableVal<OptionalInt> lineIndex;
    @Override public final OptionalInt getLineIndex() { return lineIndex.getValue(); }
    @Override public final ObservableValue<OptionalInt> lineIndexProperty() { return lineIndex; }

    private final SuspendableVal<Integer> columnPosition;
    @Override public final int getColumnPosition() { return columnPosition.getValue(); }
    @Override public final ObservableValue<Integer> columnPositionProperty() { return columnPosition; }

    private final Var<CaretVisibility> showCaret = Var.newSimpleVar(CaretVisibility.AUTO);
    @Override public final CaretVisibility getShowCaret() { return showCaret.getValue(); }
    @Override public final void setShowCaret(CaretVisibility value) { showCaret.setValue(value); }
    @Override public final Var<CaretVisibility> showCaretProperty() { return showCaret; }

    private final SuspendableVal<Optional<Bounds>> bounds;
    @Override public final Optional<Bounds> getCaretBounds() { return bounds.getValue(); }
    @Override public final ObservableValue<Optional<Bounds>> caretBoundsProperty() { return bounds; }

    private Optional<ParagraphBox.CaretOffsetX> targetOffset = Optional.empty();
    @Override public final void clearTargetOffset() { targetOffset = Optional.empty(); }
    @Override public final ParagraphBox.CaretOffsetX getTargetOffset() {
        if (!targetOffset.isPresent()) {
            targetOffset = Optional.of(area.getCaretOffsetX(this));
        }
        return targetOffset.get();
    }

    private final SuspendableNo beingUpdated = new SuspendableNo();
    @Override public final boolean isBeingUpdated() { return beingUpdated.get(); }
    @Override public final SuspendableNo beingUpdatedProperty() { return beingUpdated; }

    private final GenericStyledArea<?, ?, ?> area;
    @Override public GenericStyledArea<?, ?, ?> getArea() { return area; }

    private final String name;
    @Override public final String getCaretName() { return name; }

    private final SuspendableNo dependentBeingUpdated;
    private final EventStream<?> dirty;
    private final Var<Integer> internalTextPosition;

    private Subscription subscriptions = () -> {};

    public CaretNode(String name, GenericStyledArea<?, ?, ?> area) {
        this(name, area, 0);
    }

    public CaretNode(String name, GenericStyledArea<?, ?, ?> area, int startingPosition) {
        this(name, area, area.beingUpdatedProperty(), startingPosition);
    }

    public CaretNode(String name, GenericStyledArea<?, ?, ?> area, SuspendableNo dependentBeingUpdated, int startingPosition) {
        this.name = name;
        this.area = area;
        this.dependentBeingUpdated = dependentBeingUpdated;

        this.getStyleClass().add("caret");
        this.setManaged(false);
        this.setMouseTransparent(true); // don't want the caret to be pickable, see iss 124

        internalTextPosition = Var.newSimpleVar(startingPosition);
        position = internalTextPosition.suspendable();

        Val<TwoDimensional.Position> caretPosition2D = Val.create(
                () -> area.offsetToPosition(internalTextPosition.getValue(), Forward),
                internalTextPosition, area.getParagraphs()
        );
        paragraphIndex = caretPosition2D.map(TwoDimensional.Position::getMajor).suspendable();
        columnPosition = caretPosition2D.map(TwoDimensional.Position::getMinor).suspendable();

        // when content is updated by an area, update the caret of all the other
        // clones that also display the same document
        manageSubscription(area.multiPlainChanges(), list -> {
            int finalPosition = getPosition();
            for (PlainTextChange plainTextChange : list) {
                int netLength = plainTextChange.getNetLength();
                if (netLength != 0) {
                    int indexOfChange = plainTextChange.getPosition();
                    // in case of a replacement: "hello there" -> "hi."
                    int endOfChange = indexOfChange + Math.abs(netLength);

                    /*
{'fraction_non_alphanumeric': 0.11205191594561187, 'fraction_numerical': 0.0016069221260815822, 'mean_word_length': 3.359105603448276, 'pattern_counts': {'":': 0, '<': 67, '<?xml version=': 0, '>': 75, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4738186', 'n_tokens_mistral': 4181, 'n_tokens_neox': 4053, 'n_words': 1121}