package org.fxmisc.richtext;

import java.util.function.IntFunction;

import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;

import org.reactfx.collection.LiveList;
import org.reactfx.value.Val;

/**
 * Graphic factory that produces labels containing line numbers.
 * To customize appearance, use {@code .lineno} style class in CSS stylesheets.
 */
public class LineNumberFactory implements IntFunction<Node> {

    private static final Insets DEFAULT_INSETS = new Insets(0.0, 5.0, 0.0, 5.0);
    private static final Paint DEFAULT_TEXT_FILL = Color.web("#666");
    private static final Font DEFAULT_FONT =
            Font.font("monospace", FontPosture.ITALIC, 13);
    private static final Background DEFAULT_BACKGROUND =
            new Background(new BackgroundFill(Color.web("#ddd"), null, null));

    public static IntFunction<Node> get(StyledTextArea<?> area) {
        return get(area, digits -> "%0" + digits + "d");
    }

    public static IntFunction<Node> get(
            StyledTextArea<?> area,
            IntFunction<String> format) {
        return new LineNumberFactory(area, format);
    }

    private final Val<Integer> nParagraphs;
    private final IntFunction<String> format;

    private LineNumberFactory(
            StyledTextArea<?> area,
            IntFunction<String> format) {
        nParagraphs = LiveList.sizeOf(area.getParagraphs());
        this.format = format;
    }

    @Override
    public Node apply(int idx) {
        Label lineNo = new Label();
        lineNo.setFont(DEFAULT_FONT);
        lineNo.setBackground(DEFAULT_BACKGROUND);
        lineNo.setTextFill(DEFAULT_TEXT_FILL);
        lineNo.setPadding(DEFAULT_INSETS);
        lineNo.getStyleClass().add("lineno");

        // When removed from the scene, bind label's text to constant "",
        // which is a fake binding that consumes no resources, instead of
        // staying bound to area's paragraphs.
        lineNo.textProperty().bind(Val.flatMap(
                lineNo.sceneProperty(),
                scene -> scene != null
                        ? nParagraphs.map(n -> format(idx+1, n))
                        : Val.constant("")));
        return lineNo;
    }

    private String format(int x, int max) {
        int digits = (int) Math.floor(Math.log10(max)) + 1;
        return String.format(format.apply(digits), x);
    }
}

@@ -1702,24 +1702,97 @@
 (int idx) %7B%0A
+        Val%3CString%3E formatted = nParagraphs.map(n -%3E format(idx+1, n));%0A%0A
         Labe
@@ -1815,16 +1815,16 @@
 abel();%0A
-
         
@@ -2055,172 +2055,55 @@
  // 
-When removed from the scene, bind label's text to constant %22%22,%0A        // which is a fake binding that consumes no resources, instead of%0A        // staying bound to
+bind label's text to a Val that stops observing
  are
@@ -2116,17 +2116,16 @@
 ragraphs
-.
 %0A       
@@ -2129,61 +2129,53 @@
     
-lineNo.textProperty().bind(Val.flatMap(%0A     
+// when lineNo is removed from scene%0A
         
    l
@@ -2174,23 +2174,19 @@
     
-   
 lineNo.
-scene
+text
 Prop
@@ -2195,160 +2195,54 @@
 ty()
-,%0A                scene -%3E scene != null%0A                        ? nParagraphs.map(n -%3E format(idx+1, n))%0A                        : Val.constant(%22%22)
+.bind(formatted.conditionOnShowing(lineNo
 ));%0A
+%0A
     

Use conditionOnShowing from ReactFX in LineNumberFactory to simplify code.