diff --git a/build.gradle b/build.gradle index ef3e011..a8d8430 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ dependencies { compile group: 'com.google.code.gson', name: 'gson', version: '2.7' compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.6.2' compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.6.2' + compile group: 'org.fxmisc.richtext', name: 'richtextfx', version: '0.6.10' } task wrapper(type: Wrapper) { diff --git a/src/main/java/ninja/javafx/smartcsv/fx/validation/ValidationEditorController.java b/src/main/java/ninja/javafx/smartcsv/fx/validation/ValidationEditorController.java index 9cbdbc7..e479c51 100644 --- a/src/main/java/ninja/javafx/smartcsv/fx/validation/ValidationEditorController.java +++ b/src/main/java/ninja/javafx/smartcsv/fx/validation/ValidationEditorController.java @@ -32,18 +32,29 @@ import javafx.fxml.FXML; import javafx.scene.control.*; import ninja.javafx.smartcsv.fx.FXMLController; import ninja.javafx.smartcsv.validation.ValidationConfiguration; +import org.fxmisc.richtext.CodeArea; +import org.fxmisc.richtext.LineNumberFactory; +import org.fxmisc.richtext.StyleSpans; +import org.fxmisc.richtext.StyleSpansBuilder; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.net.URL; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.ResourceBundle; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static java.util.Arrays.asList; import static java.util.stream.Collectors.joining; /** * controller for editing column validations + * + * RichText groovy highlighting code is based on the java example of + * https://github.com/TomasMikula/RichTextFX */ @Component public class ValidationEditorController extends FXMLController { @@ -51,6 +62,39 @@ public class ValidationEditorController extends FXMLController { private StringProperty selectedColumn = new SimpleStringProperty(); private ValidationConfiguration validationConfiguration; + private static final String[] KEYWORDS = new String[] { + "abstract", "assert", "boolean", "break", "byte", + "case", "catch", "char", "class", + "continue", "def", "default", "do", "double", "else", + "enum", "extends", "false", "final", "finally", "float", + "for", "if", "implements", "import", "in", + "instanceof", "int", "interface", "length", "long", "native", + "new", "null", "package", "private", "property", "protected", "public", + "return", "short", "static", "super", + "switch", "synchronized", "this", "threadsafe", "throw", "throws", + "transient", "true", "try", "void", "volatile", "while" + }; + + private static final String KEYWORD_PATTERN = "\\b(" + String.join("|", KEYWORDS) + ")\\b"; + private static final String PAREN_PATTERN = "\\(|\\)"; + private static final String BRACE_PATTERN = "\\{|\\}"; + private static final String BRACKET_PATTERN = "\\[|\\]"; + private static final String SEMICOLON_PATTERN = "\\;"; + private static final String STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\""; + private static final String STRING2_PATTERN = "'([^'\\\\]|\\\\.)*'"; + private static final String COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/"; + + private static final Pattern PATTERN = Pattern.compile( + "(?" + KEYWORD_PATTERN + ")" + + "|(?" + PAREN_PATTERN + ")" + + "|(?" + BRACE_PATTERN + ")" + + "|(?" + BRACKET_PATTERN + ")" + + "|(?" + SEMICOLON_PATTERN + ")" + + "|(?" + STRING_PATTERN + ")" + + "|(?" + STRING2_PATTERN + ")" + + "|(?" + COMMENT_PATTERN + ")" + ); + @FXML private CheckBox notEmptyRuleCheckBox; @@ -79,7 +123,7 @@ public class ValidationEditorController extends FXMLController { private TextField valueOfRuleTextField; @FXML - private TextArea groovyRuleTextArea; + private CodeArea groovyRuleTextArea; @FXML private CheckBox enableNotEmptyRule; @@ -133,7 +177,7 @@ public class ValidationEditorController extends FXMLController { initTextInputControl(dateformatRuleTextField, enableDateRule); initTextInputControl(regexpRuleTextField, enableRegexpRule); initTextInputControl(valueOfRuleTextField, enableValueOfRule); - initTextInputControl(groovyRuleTextArea, enableGroovyRule); + initCodeAreaControl(groovyRuleTextArea, enableGroovyRule); selectedColumn.addListener(observable -> { updateForm(); @@ -275,7 +319,7 @@ public class ValidationEditorController extends FXMLController { enableValueOfRule ); - updateTextInputControl( + updateCodeAreaControl( groovyRuleTextArea, validationConfiguration.getGroovyRuleFor(getSelectedColumn()), enableGroovyRule @@ -318,6 +362,15 @@ public class ValidationEditorController extends FXMLController { } } + private void updateCodeAreaControl(CodeArea rule, String value, CheckBox ruleEnabled) { + if (value == null) { + ruleEnabled.setSelected(false); + } else { + ruleEnabled.setSelected(true); + rule.replaceText(0, 0, value); + } + } + private void initCheckBox(CheckBox rule, CheckBox ruleEnabled) { rule.disableProperty().bind(ruleEnabled.selectedProperty().not()); ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> { @@ -345,4 +398,43 @@ public class ValidationEditorController extends FXMLController { }); } + private void initCodeAreaControl(CodeArea rule, CheckBox ruleEnabled) { + rule.disableProperty().bind(ruleEnabled.selectedProperty().not()); + ruleEnabled.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (!newValue) { + rule.clear(); + } + }); + rule.setParagraphGraphicFactory(LineNumberFactory.get(rule)); + rule.richChanges() + .filter(ch -> !ch.getInserted().equals(ch.getRemoved())) // XXX + .subscribe(change -> { + rule.setStyleSpans(0, computeHighlighting(rule.getText())); + }); + } + + private static StyleSpans> computeHighlighting(String text) { + Matcher matcher = PATTERN.matcher(text); + int lastKwEnd = 0; + StyleSpansBuilder> spansBuilder + = new StyleSpansBuilder<>(); + while(matcher.find()) { + String styleClass = + matcher.group("KEYWORD") != null ? "keyword" : + matcher.group("PAREN") != null ? "paren" : + matcher.group("BRACE") != null ? "brace" : + matcher.group("BRACKET") != null ? "bracket" : + matcher.group("SEMICOLON") != null ? "semicolon" : + matcher.group("STRING") != null ? "string" : + matcher.group("STRING2") != null ? "string" : + matcher.group("COMMENT") != null ? "comment" : + null; /* never happens */ assert styleClass != null; + spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd); + spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start()); + lastKwEnd = matcher.end(); + } + spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd); + return spansBuilder.create(); + } + } diff --git a/src/main/resources/ninja/javafx/smartcsv/fx/about/about.fxml b/src/main/resources/ninja/javafx/smartcsv/fx/about/about.fxml index 8e4516a..20ed5f5 100644 --- a/src/main/resources/ninja/javafx/smartcsv/fx/about/about.fxml +++ b/src/main/resources/ninja/javafx/smartcsv/fx/about/about.fxml @@ -58,6 +58,7 @@ + diff --git a/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.css b/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.css index 77951a5..a3d6aa1 100644 --- a/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.css +++ b/src/main/resources/ninja/javafx/smartcsv/fx/smartcsv.css @@ -179,4 +179,35 @@ -fx-text-fill: black; -fx-font-size: 12px; -fx-padding: 10 10 10 10; +} + +.keyword { + -fx-fill: purple; + -fx-font-weight: bold; +} +.semicolon { + -fx-font-weight: bold; +} +.paren { + -fx-fill: firebrick; + -fx-font-weight: bold; +} +.bracket { + -fx-fill: darkgreen; + -fx-font-weight: bold; +} +.brace { + -fx-fill: teal; + -fx-font-weight: bold; +} +.string { + -fx-fill: blue; +} + +.comment { + -fx-fill: cadetblue; +} + +.paragraph-box:has-caret { + -fx-background-color: #f2f9fc; } \ No newline at end of file diff --git a/src/main/resources/ninja/javafx/smartcsv/fx/validation/validationEditor.fxml b/src/main/resources/ninja/javafx/smartcsv/fx/validation/validationEditor.fxml index 09901ce..d827480 100644 --- a/src/main/resources/ninja/javafx/smartcsv/fx/validation/validationEditor.fxml +++ b/src/main/resources/ninja/javafx/smartcsv/fx/validation/validationEditor.fxml @@ -10,6 +10,8 @@ + + @@ -50,7 +52,7 @@ -