Search in sources :

Example 1 with ScriptEditorControl

use of qupath.lib.gui.scripting.ScriptEditorControl in project qupath by qupath.

the class RichScriptEditor method getNewEditor.

@Override
protected ScriptEditorControl getNewEditor() {
    try {
        CodeArea codeArea = new CustomCodeArea();
        CodeAreaControl control = new CodeAreaControl(codeArea);
        /*
			 * Using LineNumberFactory.get(codeArea) gives errors related to the new paragraph folding introduced in RichTextFX 0.10.6.
			 *  java.lang.IllegalArgumentException: Visible paragraphs' last index is [-1] but visibleParIndex was [0]
			 *  
			 * To replicate
			 *  - Run using codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea));
			 *  - Add some code (including line breaks) to the code area
			 *  - Select all text (Ctrl/Cmd + A)
			 *  - Delete text (backspace)
			 *  - Add more text
			 *  
			 * The change below avoids code folding being used.
			 */
        codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea, digits -> "%1$" + digits + "s", null, null));
        // codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea));
        codeArea.setStyle("-fx-background-color: -fx-control-inner-background;");
        // Catch key typed events for special character handling (which should be platform-agnostic)
        codeArea.addEventFilter(KeyEvent.KEY_TYPED, e -> {
            if (e.isConsumed())
                return;
            var scriptSyntax = currentLanguage.get().getSyntax();
            if ("(".equals(e.getCharacter())) {
                scriptSyntax.handleLeftParenthesis(control, smartEditing.get());
                e.consume();
            } else if (")".equals(e.getCharacter())) {
                scriptSyntax.handleRightParenthesis(control, smartEditing.get());
                e.consume();
            } else if ("\"".equals(e.getCharacter())) {
                scriptSyntax.handleQuotes(control, true, smartEditing.get());
                e.consume();
            } else if ("\'".equals(e.getCharacter())) {
                scriptSyntax.handleQuotes(control, false, smartEditing.get());
                e.consume();
            }
        });
        // TODO: Check if DefaultScriptEditor does any of these? It should be able to at least do syntaxing/auto-completion
        codeArea.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
            if (e.isConsumed())
                return;
            var scriptSyntax = currentLanguage.get().getSyntax();
            if (scriptSyntax != null) {
                if (e.getCode() == KeyCode.TAB) {
                    scriptSyntax.handleTabPress(control, e.isShiftDown());
                    e.consume();
                } else if (e.isShortcutDown() && e.getCode() == KeyCode.SLASH) {
                    scriptSyntax.handleLineComment(control);
                    e.consume();
                } else if (e.getCode() == KeyCode.ENTER && codeArea.getSelectedText().length() == 0) {
                    scriptSyntax.handleNewLine(control, smartEditing.get());
                    e.consume();
                } else if (e.getCode() == KeyCode.BACK_SPACE) {
                    if (scriptSyntax.handleBackspace(control, smartEditing.get()) && !e.isShortcutDown() && !e.isShiftDown())
                        e.consume();
                } else if (beautifyerCodeCombination.match(e)) {
                    getCurrentTextComponent().setText(scriptSyntax.beautify(getCurrentText()));
                    e.isConsumed();
                }
            }
            var scriptAutoCompletor = currentLanguage.get().getAutoCompletor();
            if (scriptAutoCompletor != null) {
                if (completionCodeCombination.match(e)) {
                    scriptAutoCompletor.applyNextCompletion(control);
                    e.isConsumed();
                } else if (!e.isControlDown())
                    scriptAutoCompletor.resetCompletion();
            }
        });
        codeArea.setOnContextMenuRequested(e -> menu.show(codeArea.getScene().getWindow(), e.getScreenX(), e.getScreenY()));
        @SuppressWarnings("unused") var cleanup = codeArea.multiPlainChanges().successionEnds(Duration.ofMillis(delayMillis)).supplyTask(() -> {
            Task<StyleSpans<Collection<String>>> task = new Task<>() {

                @Override
                protected StyleSpans<Collection<String>> call() {
                    return scriptHighlighter.get().computeEditorHighlighting(codeArea.getText());
                }
            };
            executor.execute(task);
            return task;
        }).awaitLatest(codeArea.multiPlainChanges()).filterMap(t -> {
            if (t.isSuccess())
                return Optional.of(t.get());
            var exception = t.getFailure();
            String message = exception.getLocalizedMessage() == null ? exception.getClass().getSimpleName() : exception.getLocalizedMessage();
            logger.error("Error applying syntax highlighting: {}", message);
            logger.debug("{}", t);
            return Optional.empty();
        }).subscribe(change -> codeArea.setStyleSpans(0, change));
        codeArea.getStylesheets().add(getClass().getClassLoader().getResource("scripting_styles.css").toExternalForm());
        scriptHighlighter.bind(Bindings.createObjectBinding(() -> ScriptHighlighterProvider.getHighlighterFromLanguage(getCurrentLanguage()), currentLanguage));
        // Triggered whenever the script styling changes (e.g. change of language)
        scriptHighlighter.addListener((v, o, n) -> {
            if (n == null || (o != null && o.getClass().equals(n.getClass())))
                return;
            StyleSpans<Collection<String>> changes = scriptHighlighter.get().computeEditorHighlighting(codeArea.getText());
            codeArea.setStyleSpans(0, changes);
        });
        return control;
    } catch (Exception e) {
        // Default to superclass implementation
        logger.error("Unable to create code area", e);
        return super.getNewEditor();
    }
}
Also used : KeyCode(javafx.scene.input.KeyCode) ScriptHighlighterProvider(qupath.lib.gui.scripting.highlighters.ScriptHighlighterProvider) ObjectProperty(javafx.beans.property.ObjectProperty) Logger(org.slf4j.Logger) Collection(java.util.Collection) StyleSpans(org.fxmisc.richtext.model.StyleSpans) LoggerFactory(org.slf4j.LoggerFactory) CodeArea(org.fxmisc.richtext.CodeArea) KeyEvent(javafx.scene.input.KeyEvent) Bindings(javafx.beans.binding.Bindings) MenuTools(qupath.lib.gui.tools.MenuTools) Executors(java.util.concurrent.Executors) LineNumberFactory(org.fxmisc.richtext.LineNumberFactory) Task(javafx.concurrent.Task) ScriptHighlighter(qupath.lib.gui.scripting.highlighters.ScriptHighlighter) SimpleObjectProperty(javafx.beans.property.SimpleObjectProperty) ContextMenu(javafx.scene.control.ContextMenu) Duration(java.time.Duration) Optional(java.util.Optional) ThreadTools(qupath.lib.common.ThreadTools) DefaultScriptEditor(qupath.lib.gui.scripting.DefaultScriptEditor) ScriptEditorControl(qupath.lib.gui.scripting.ScriptEditorControl) ExecutorService(java.util.concurrent.ExecutorService) QuPathGUI(qupath.lib.gui.QuPathGUI) Task(javafx.concurrent.Task) CodeArea(org.fxmisc.richtext.CodeArea) StyleSpans(org.fxmisc.richtext.model.StyleSpans) Collection(java.util.Collection)

Aggregations

Duration (java.time.Duration)1 Collection (java.util.Collection)1 Optional (java.util.Optional)1 ExecutorService (java.util.concurrent.ExecutorService)1 Executors (java.util.concurrent.Executors)1 Bindings (javafx.beans.binding.Bindings)1 ObjectProperty (javafx.beans.property.ObjectProperty)1 SimpleObjectProperty (javafx.beans.property.SimpleObjectProperty)1 Task (javafx.concurrent.Task)1 ContextMenu (javafx.scene.control.ContextMenu)1 KeyCode (javafx.scene.input.KeyCode)1 KeyEvent (javafx.scene.input.KeyEvent)1 CodeArea (org.fxmisc.richtext.CodeArea)1 LineNumberFactory (org.fxmisc.richtext.LineNumberFactory)1 StyleSpans (org.fxmisc.richtext.model.StyleSpans)1 Logger (org.slf4j.Logger)1 LoggerFactory (org.slf4j.LoggerFactory)1 ThreadTools (qupath.lib.common.ThreadTools)1 QuPathGUI (qupath.lib.gui.QuPathGUI)1 DefaultScriptEditor (qupath.lib.gui.scripting.DefaultScriptEditor)1