package com.scrivenvar.editor;
import static com.scrivenvar.Constants.STYLESHEET_EDITOR;
import com.scrivenvar.dialogs.ImageDialog;
import com.scrivenvar.dialogs.LinkDialog;
import com.scrivenvar.processors.MarkdownProcessor;
import static com.scrivenvar.util.Utils.ltrim;
import static com.scrivenvar.util.Utils.rtrim;
import com.vladsch.flexmark.ast.Link;
import com.vladsch.flexmark.ast.Node;
import java.nio.file.Path;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Dialog;
import javafx.scene.control.IndexRange;
import static javafx.scene.input.KeyCode.ENTER;
import javafx.scene.input.KeyEvent;
import javafx.stage.Window;
import org.fxmisc.richtext.StyleClassedTextArea;
import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed;
public class MarkdownEditorPane extends EditorPane {
private static final Pattern AUTO_INDENT_PATTERN = Pattern.compile(
"(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" );
public MarkdownEditorPane() {
initEditor();
}
private void initEditor() {
final StyleClassedTextArea textArea = getEditor();
textArea.setWrapText( true );
textArea.getStyleClass().add( "markdown-editor" );
textArea.getStylesheets().add( STYLESHEET_EDITOR );
addEventListener( keyPressed( ENTER ), this::enterPressed );
}
public ObservableValue<String> markdownProperty() {
return getEditor().textProperty();
}
private void enterPressed( final KeyEvent e ) {
final StyleClassedTextArea textArea = getEditor();
final String currentLine = textArea.getText( textArea.getCurrentParagraph() );
final Matcher matcher = AUTO_INDENT_PATTERN.matcher( currentLine );
String newText = "\n";
if( matcher.matches() ) {
if( !matcher.group( 2 ).isEmpty() ) {
newText = newText.concat( matcher.group( 1 ) );
} else {
final int caretPosition = textArea.getCaretPosition();
textArea.selectRange( caretPosition - currentLine.length(), caretPosition );
}
}
textArea.replaceSelection( newText );
}
public void surroundSelection( final String leading, final String trailing ) {
surroundSelection( leading, trailing, null );
}
public void surroundSelection( String leading, String trailing, final String hint ) {
final StyleClassedTextArea textArea = getEditor();
final IndexRange selection = textArea.getSelection();
int start = selection.getStart();
int end = selection.getEnd();
final String selectedText = textArea.getSelectedText();
String trimmedSelectedText = selectedText.trim();
if( trimmedSelectedText.length() < selectedText.length() ) {
start += selectedText.indexOf( trimmedSelectedText );
end = start + trimmedSelectedText.length();
}
if( start == 0 ) {
leading = ltrim( leading );
}
if( end == textArea.getLength() ) {
trailing = rtrim( trailing );
}
if( leading.startsWith( "\n" ) ) {
for( int i = start - 1; i >= 0 && leading.startsWith( "\n" ); i-- ) {
if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) {
break;
}
leading = leading.substring( 1 );
}
}
final boolean trailingIsEmpty = trailing.isEmpty();
String str = trailingIsEmpty ? leading : trailing;
if( str.endsWith( "\n" ) ) {
final int length = textArea.getLength();
for( int i = end; i < length && str.endsWith( "\n" ); i++ ) {
if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) {
break;
}
str = str.substring( 0, str.length() - 1 );
}
if( trailingIsEmpty ) {
leading = str;
} else {
trailing = str;
}
}
int selStart = start + leading.length();
int selEnd = end + leading.length();
if( hint != null && trimmedSelectedText.isEmpty() ) {
trimmedSelectedText = hint;
selEnd = selStart + hint.length();
}
getUndoManager().preventMerge();
textArea.replaceText( start, end, leading + trimmedSelectedText + trailing );
textArea.selectRange( selStart, selEnd );
}
private HyperlinkModel getHyperlink() {
final StyleClassedTextArea textArea = getEditor();
final String selectedText = textArea.getSelectedText();
final MarkdownProcessor mp = new MarkdownProcessor( null );
final int p = textArea.getCurrentParagraph();
final String paragraph = textArea.getText( p );
final Node node = mp.toNode( paragraph );
final LinkVisitor visitor = new LinkVisitor( textArea.getCaretColumn() );
final Link link = visitor.process( node );
if( link != null ) {
textArea.selectRange( p, link.getStartOffset(), p, link.getEndOffset() );
}
final HyperlinkModel model = createHyperlinkModel(
link, selectedText, "https://website.com"
);
return model;
}
private HyperlinkModel createHyperlinkModel(
final Link link, final String selection, final String url ) {
return link == null
? new HyperlinkModel( selection, url )
: new HyperlinkModel( link );
}
private Path getParentPath() {
final Path parentPath = getPath();
return (parentPath != null) ? parentPath.getParent() : null;
}
private Dialog<String> createLinkDialog() {
return new LinkDialog( getWindow(), getHyperlink(), getParentPath() );
}
private Dialog<String> createImageDialog() {
return new ImageDialog( getWindow(), getParentPath() );
}
private void insertObject( final Dialog<String> dialog ) {
dialog.showAndWait().ifPresent( result -> {
getEditor().replaceSelection( result );
} );
}
public void insertLink() {
insertObject( createLinkDialog() );
}
public void insertImage() {
insertObject( createImageDialog() );
}
private Window getWindow() {
return getScrollPane().getScene().getWindow();
}
}