<commit_before>package jp.toastkid.article.control.editor;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.eclipse.collections.impl.factory.Maps;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXComboBox;
import com.sun.javafx.PlatformUtil;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Worker.State;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.input.InputMethodEvent;
import javafx.scene.input.InputMethodTextRun;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import jp.toastkid.article.models.Article;
import jp.toastkid.jfx.common.FontFactory;
import jp.toastkid.jfx.common.control.NumberTextField;
import jp.toastkid.jfx.common.transition.SplitterTransitionFactory;
import jp.toastkid.libs.utils.MathUtil;
import jp.toastkid.libs.utils.Strings;
import jp.toastkid.yobidashi.models.Config;
import jp.toastkid.yobidashi.models.Config.Key;
import jp.toastkid.yobidashi.models.Defines;

/**
 * Markdown editor.
 *
 * @author <NAME>
 *
 */
public class Editor {

    /** Logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(Editor.class);

    /** Editor header's class. */
    private static final String DEFAULT_STYLE_CLASS_EDITOR_HEADER        = "editor-header";

    /** Editor label's class. */
    private static final String DEFAULT_STYLE_CLASS_EDITOR_LABEL         = "editor-label";

    /** Editor button's class. */
    private static final String DEFAULT_STYLE_CLASS_EDITOR_APPLY_BUTTON  = "editor-button";

    /** Default scale factor. */
    private static final double DEFAULT_SCALE_FACTOR = 2.2d;

    /** Config file's path. */
    private static final Path CONFIG_PATH = Defines.CONFIG;

    /** WebView - Editor y pos. */
    private double scaleFactor = DEFAULT_SCALE_FACTOR;

    /** File Path. */
    private final Path path;

    /** Splitter. */
    private final SplitPane split;

    /** Content editor. */
    private final CodeArea area;

    /** Editor's scroll bar. */
    private final VirtualizedScrollPane<CodeArea> vsp;

    /** Start of the text under input method composition. */
    private int imstart;

    /** Length of the text under input method composition. */
    private int imlength;

    /** Holds concrete attributes for the composition runs. */
    private final List<Shape> imattrs = new ArrayList<Shape>();

    /** Modified value. */
    private final BooleanProperty isModified;

    /** WebView engine. */
    private final WebEngine engine;

    /** Preview's html height. */
    private double previewHeight;

    /** Editor pane. */
    private final BorderPane editorBox;

    /** Editor's head label. */
    private Label label;

    /** Font family */
    private ComboBox<String> fontFamily;

    /** Font size. */
    private NumberTextField fontSize;

    /** Use for control font. */
    private Config conf;

    /**
     * Initialize with path.
     * @param path
     */
    Editor(final Path path, final WebView webView) {
        this.path      = path;
        this.engine    = webView.getEngine();
        this.area      = new CodeArea();

        initArea();

        engine.getLoadWorker().stateProperty().addListener((observable, prev, next) -> {
            if (!State.SUCCEEDED.equals(observable.getValue())) {
                return;
            }
            previewHeight
                = Double.parseDouble(engine.executeScript("document.body.scrollHeight").toString());
        });

        vsp = new VirtualizedScrollPane<>(area);
        vsp.estimatedScrollYProperty().addListener(
                (value, prev, next) -> scrollTo(convertToWebViewY(value.getValue().doubleValue())));

        conf = new Config(CONFIG_PATH);

        editorBox = new BorderPane();
        editorBox.setTop(makeHeader());
        editorBox.setCenter(vsp);

        split = new SplitPane(editorBox, webView);
        split.setDividerPositions(0.5);

        isModified = new SimpleBooleanProperty(false);
    }

    /**
     * Make editor's header.
     * @return header
     */
    private HBox makeHeader() {
        this.label = new Label();
        this.label.getStyleClass().add(DEFAULT_STYLE_CLASS_EDITOR_LABEL);
        this.label.setStyle("-fx-font-size: 14pt;");

        this.fontFamily = new JFXComboBox<>();
        this.fontFamily.getItems().addAll(Font.getFamilies());

        this.fontSize = new NumberTextField();
        this.fontSize.setOnAction(event -> applyFontSettings());

        final Button button = new JFXButton("Apply");
        button.setOnAction(event -> applyFontSettings());
        button.getStyleClass().add(DEFAULT_STYLE_CLASS_EDITOR_APPLY_BUTTON);

        final HBox header = new HBox(label, fontFamily, fontSize, button);
        header.setAlignment(Pos.CENTER);
        header.getStyleClass().add(DEFAULT_STYLE_CLASS_EDITOR_HEADER);
        return header;
    }

    /**
     * Initialize text area.
     */
    private void initArea() {
        area.setWrapText(true);
        area.setParagraphGraphicFactory(LineNumberFactory.get(area));
        area.setOnKeyPressed(event -> {
            if (event.isControlDown()) {
                return;
            }

            if (!event.getCode().isLetterKey()
                    && !event.getCode().isDigitKey()
                    && !event.getCode().isWhitespaceKey()) {
                return;
            }
            isModified.set(true);
        });

        area.textProperty().addListener((value, prev, next) -> {
            label.setText(String.format("文字数: %,d字", value.getValue().length()));
        });

        if (area.getOnInputMethodTextChanged() == null) {
            area.setOnInputMethodTextChanged(this::handleInputMethodEvent);
        }

        area.setInputMethodRequests(new EditorInputMethodRequests(area));
        area.totalHeightEstimateProperty().addListener((value, prev, next) -> {
            if (next == null) {
                return;
            }
            final double newValue = previewHeight / next;
            scaleFactor = 0 < newValue
                    ? 1 < newValue ? newValue * 1.2d : newValue * 0.8d
                    : DEFAULT_SCALE_FACTOR;
        });
    }

    /**
     * Handle input method event.
     * @param event
     */
    private void handleInputMethodEvent(final InputMethodEvent event) {
        if (!area.isEditable() || area.isDisabled()) {
            return;<commit_msg>modify editor's header.<commit_after>package jp.toastkid.article.control.editor;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.eclipse.collections.impl.factory.Maps;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXComboBox;
import com.sun.javafx.PlatformUtil;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Worker.State;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.SplitPane;
import javafx.scene.input.InputMethodEvent;
import javafx.scene.input.InputMethodTextRun;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import jp.toastkid.article.models.Article;
import jp.toastkid.jfx.common.FontFactory;
import jp.toastkid.jfx.common.control.NumberTextField;
import jp.toastkid.jfx.common.transition.SplitterTransitionFactory;
import jp.toastkid.libs.utils.MathUtil;
import jp.toastkid.libs.utils.Strings;
import jp.toastkid.yobidashi.models.Config;
import jp.toastkid.yobidashi.models.Config.Key;
import jp.toastkid.yobidashi.models.Defines;

/**
 * Markdown editor.
 *
 * @author <NAME>
 *
 */
public class Editor {

    /** Logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(Editor.class);

    /** Editor header's class. */
    private static final String DEFAULT_STYLE_CLASS_EDITOR_HEADER        = "editor-header";

    /** Editor label's class. */
    private static final String DEFAULT_STYLE_CLASS_EDITOR_LABEL         = "editor-label";

    /** Editor button's class. */
    private static final String DEFAULT_STYLE_CLASS_EDITOR_APPLY_BUTTON  = "editor-button";

    /** Default scale factor. */
    private static final double DEFAULT_SCALE_FACTOR = 2.2d;

    /** Config file's path. */
    private static final Path CONFIG_PATH = Defines.CONFIG;

    /** WebView - Editor y pos. */
    private double scaleFactor = DEFAULT_SCALE_FACTOR;

    /** File Path. */
    private final Path path;

    /** Splitter. */
    private final SplitPane split;

    /** Content editor. */
    private final CodeArea area;

    /** Editor's scroll bar. */
    private final VirtualizedScrollPane<CodeArea> vsp;

    /** Start of the text under input method composition. */
    private int imstart;

    /** Length of the text under input method composition. */
    private int imlength;

    /** Holds concrete attributes for the composition runs. */
    private final List<Shape> imattrs = new ArrayList<Shape>();

    /** Modified value. */
    private final BooleanProperty isModified;

    /** WebView engine. */
    private final WebEngine engine;

    /** Preview's html height. */
    private double previewHeight;

    /** Editor pane. */
    private final BorderPane editorBox;

    /** Editor's head label. */
    private Label label;

    /** Font family */
    private ComboBox<String> fontFamily;

    /** Font size. */
    private NumberTextField fontSize;

    /** Use for control font. */
    private Config conf;

    /**
     * Initialize with path.
     * @param path
     */
    Editor(final Path path, final WebView webView) {
        this.path      = path;
        this.engine    = webView.getEngine();
        this.area      = new CodeArea();

        initArea();

        engine.getLoadWorker().stateProperty().addListener((observable, prev, next) -> {
            if (!State.SUCCEEDED.equals(observable.getValue())) {
                return;
            }
            previewHeight
                = Double.parseDouble(engine.executeScript("document.body.scrollHeight").toString());
        });

        vsp = new VirtualizedScrollPane<>(area);
        vsp.estimatedScrollYProperty().addListener(
                (value, prev, next) -> scrollTo(convertToWebViewY(value.getValue().doubleValue())));

        conf = new Config(CONFIG_PATH);

        editorBox = new BorderPane();
        editorBox.setTop(makeHeader());
        editorBox.setCenter(vsp);

        split = new SplitPane(editorBox, webView);
        split.setDividerPositions(0.5);

        isModified = new SimpleBooleanProperty(false);
    }

    /**
     * Make editor's header.
     * @return header
     */
    private HBox makeHeader() {
        this.label = new Label();
        this.label.getStyleClass().add(DEFAULT_STYLE_CLASS_EDITOR_LABEL);
        this.label.setStyle("-fx-font-size: 14pt;");

        this.fontFamily = new JFXComboBox<>();
        this.fontFamily.getItems().addAll(Font.getFamilies());

        this.fontSize = new NumberTextField();
        this.fontSize.setMaxWidth(50.0);
        this.fontSize.setOnAction(event -> applyFontSettings());

        final Button button = new JFXButton("Apply");
        button.setOnAction(event -> applyFontSettings());
        button.getStyleClass().add(DEFAULT_STYLE_CLASS_EDITOR_APPLY_BUTTON);

        final HBox header = new HBox(label, fontFamily, fontSize, button);
        header.getStyleClass().add(DEFAULT_STYLE_CLASS_EDITOR_HEADER);
        header.setAlignment(Pos.CENTER_LEFT);
        header.setSpacing(10.0d);
        return header;
    }

    /**
     * Initialize text area.
     */
    private void initArea() {
        area.setWrapText(true);
        area.setParagraphGraphicFactory(LineNumberFactory.get(area));
        area.setOnKeyPressed(event -> {
            if (event.isControlDown()) {
                return;
            }

            if (!event.getCode().isLetterKey()
                    && !event.getCode().isDigitKey()
                    && !event.getCode().isWhitespaceKey()) {
                return;
            }
            isModified.set(true);
        });

        area.textProperty().addListener((value, prev, next) -> {
            label.setText(String.format("  文字数: %,d字", value.getValue().length()));
        });

        if (area.getOnInputMethodTextChanged() == null) {
            area.setOnInputMethodTextChanged(this::handleInputMethodEvent);
        }

        area.setInputMethodRequests(new EditorInputMethodRequests(area));
        area.totalHeightEstimateProperty().addListener((value, prev, next) -> {
            if (next == null) {
                return;
            }
            final double newValue = previewHeight / next;
            scaleFactor = 0 < newValue
                    ? 1 < newValue ? newValue * 1.2d : newValue * 0.8d
                    : DEFAULT_SCALE_FACTOR;
        });
    }

    /**
     * Handle input method event.
     * @param event
     */
    private void handleInputMethodEvent(final InputMethodEvent event) {
        if (!area.isEditable() || area.isDisabled()) {
            return;
{'fraction_non_alphanumeric': 0.11668994413407821, 'fraction_numerical': 0.0023743016759776537, 'mean_word_length': 2.736237933733368, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5710853', 'n_tokens_mistral': 4255, 'n_tokens_neox': 4074, 'n_words': 844}