diff --git a/src/main/java/eu/mihosoft/monacofx/ClipboardBridge.java b/src/main/java/eu/mihosoft/monacofx/ClipboardBridge.java index f46c6cc..1c1b424 100644 --- a/src/main/java/eu/mihosoft/monacofx/ClipboardBridge.java +++ b/src/main/java/eu/mihosoft/monacofx/ClipboardBridge.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; /** * Bridge between javascript code and java to add and use system clipboard functionality. @@ -54,7 +55,7 @@ public void copy(JSObject jsSelection) { int endLineNumber = getNumber(jsSelection, "endLineNumber") - 1; int endColumn = getNumber(jsSelection, "endColumn") - 1; String originText = document.getText(); - String[] lines = originText.split("\n"); + String[] lines = originText.split("\n", -1); StringBuilder copyText = new StringBuilder(); if (startLineNumber == endLineNumber) { copyText = new StringBuilder(lines[startLineNumber].substring(startColumn, endColumn)); @@ -112,14 +113,20 @@ private String addPasteString(JSObject jsSelection, String pasteString, String o private void calcNewCursorPosition(JSObject position, String string) { int lineNumber = getNumber(position, "lineNumber"); int column = getNumber(position, "column"); - long count = string.lines().count() - 1; + long count = string.split("\n", -1).length - 1; position.setMember("lineNumber", lineNumber + count); - position.setMember("column", column + string.lines().skip(count).findFirst().get().length()); + + Optional lastLine = string.lines().skip(count).findFirst(); + if (lastLine.isPresent()) { + position.setMember("column", column + lastLine.get().length()); + } else { + position.setMember("column", column + string.length()); + } } - private int getNumber(JSObject selection, String startLineNumber) { + private int getNumber(JSObject selection, String numberName) { int number = 0; - Object selectionMember = selection.getMember(startLineNumber); + Object selectionMember = selection.getMember(numberName); if (selectionMember instanceof Integer) { number = (Integer) selectionMember; } else if (selectionMember instanceof Double) { diff --git a/src/main/java/eu/mihosoft/monacofx/Document.java b/src/main/java/eu/mihosoft/monacofx/Document.java index 6bfaea2..a052341 100644 --- a/src/main/java/eu/mihosoft/monacofx/Document.java +++ b/src/main/java/eu/mihosoft/monacofx/Document.java @@ -123,6 +123,12 @@ public String getLanguage() { * @param text the text in editor is replaced byt this text */ public void updateText(String text) { - window.call("updateText", text); + try { + window.call("updateText", text); + } catch (JSException jsException) { + // in some cases the window object gets lost. why? this is a workaround + window = (JSObject) engine.executeScript("window"); + window.call("updateText", text); + } } } diff --git a/src/main/java/eu/mihosoft/monacofx/MonacoFX.java b/src/main/java/eu/mihosoft/monacofx/MonacoFX.java index 7189728..35515e3 100755 --- a/src/main/java/eu/mihosoft/monacofx/MonacoFX.java +++ b/src/main/java/eu/mihosoft/monacofx/MonacoFX.java @@ -23,19 +23,23 @@ */ package eu.mihosoft.monacofx; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; import javafx.application.Platform; import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.value.ChangeListener; import javafx.concurrent.Worker; +import javafx.event.ActionEvent; import javafx.geometry.HPos; import javafx.geometry.VPos; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; -import javafx.scene.input.ScrollEvent; import javafx.scene.layout.Region; import javafx.scene.robot.Robot; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.util.Callback; +import javafx.util.Duration; import netscape.javascript.JSException; import netscape.javascript.JSObject; @@ -59,6 +63,9 @@ public class MonacoFX extends Region { private final SystemClipboardWrapper systemClipboardWrapper; private boolean readOnly; + private Worker.State workerState; + private Timeline oneSecondWonder; + public MonacoFX() { view = new WebView(); @@ -71,6 +78,7 @@ public MonacoFX() { systemClipboardWrapper = new SystemClipboardWrapper(); ClipboardBridge clipboardBridge = new ClipboardBridge(getEditor().getDocument(), systemClipboardWrapper); engine.getLoadWorker().stateProperty().addListener((o, old, state) -> { + workerState = state; if (state == Worker.State.SUCCEEDED) { AtomicBoolean jsDone = new AtomicBoolean(false); AtomicInteger attempts = new AtomicInteger(); @@ -105,16 +113,30 @@ public MonacoFX() { } }); engine.load(url); + waitForLoad(); addClipboardFunctions(); } + /** + * wait a bit + */ + private void waitForLoad() { + oneSecondWonder = new Timeline(new KeyFrame(Duration.seconds(1), (ActionEvent event) -> { + if ( Worker.State.SUCCEEDED == workerState) { + oneSecondWonder.stop(); + } + })); + oneSecondWonder.setCycleCount(15); + oneSecondWonder.play(); + } + public void reload() { engine.reload(); setReadonly(isReadOnly()); } private void addClipboardFunctions() { - addEventFilter(KeyEvent.KEY_PRESSED, event -> systemClipboardWrapper.handleCopyCutKeyEvent(event, (a) -> getSelectionObject())); + addEventFilter(KeyEvent.KEY_PRESSED, event -> systemClipboardWrapper.handleCopyCutKeyEvent(event, (a) -> getSelectionObject(), readOnly)); } private Object getSelectionObject() { @@ -130,11 +152,10 @@ private static void pressArrowKey(Robot r, KeyCode keyCode, int count) { @Override public void requestFocus() { - getWebEngine().getLoadWorker().stateProperty().addListener((o, old, state) -> { - if (state == Worker.State.SUCCEEDED) { - super.requestFocus(); - getWebEngine().executeScript("setTimeout(() => { editorView.focus();}, 1200);"); - } + executeJavaScriptLambda(null, param -> { + super.requestFocus(); + getWebEngine().executeScript("setTimeout(() => { editorView.focus();}, 1200);"); + return null; }); } @@ -248,22 +269,22 @@ private void executeJavaScriptLambda(Object parameter , Callback c if (Worker.State.SUCCEEDED == stateProperty.getValue()) { callback.call(parameter); } else { - getWebEngine().getLoadWorker().stateProperty().addListener((o, old, state) -> { + ChangeListener stateChangeListener = (o, old, state) -> { if (Worker.State.SUCCEEDED == state) { - JSObject window = (JSObject) engine.executeScript("window"); AtomicBoolean jsDone = new AtomicBoolean(false); AtomicInteger attempts = new AtomicInteger(); Thread thread = new Thread(() -> { while (!jsDone.get()) { // check if JS execution is done. Platform.runLater(() -> { + JSObject window = (JSObject) engine.executeScript("window"); Object jsEditorObj = window.call("getEditorView"); if (jsEditorObj instanceof JSObject) { callback.call(parameter); jsDone.set(true); } }); - if(attempts.getAndIncrement()> 10) { + if (attempts.getAndIncrement() > 10) { throw new RuntimeException( "Cannot initialize editor (JS execution not complete). Max number of attempts reached." ); @@ -279,7 +300,8 @@ private void executeJavaScriptLambda(Object parameter , Callback c }); thread.start(); } - }); + }; + stateProperty.addListener(stateChangeListener); } } } diff --git a/src/main/java/eu/mihosoft/monacofx/SystemClipboardWrapper.java b/src/main/java/eu/mihosoft/monacofx/SystemClipboardWrapper.java index 1528cfd..258b43f 100644 --- a/src/main/java/eu/mihosoft/monacofx/SystemClipboardWrapper.java +++ b/src/main/java/eu/mihosoft/monacofx/SystemClipboardWrapper.java @@ -40,6 +40,7 @@ public class SystemClipboardWrapper { private final KeyCodeCombination KEY_CODE_CTRL_C = new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN); private final KeyCodeCombination KEY_CODE_CTRL_X = new KeyCodeCombination(KeyCode.X, KeyCombination.SHORTCUT_DOWN); private final KeyCodeCombination KEY_CODE_CTRL_INSERT = new KeyCodeCombination(KeyCode.INSERT, KeyCombination.SHORTCUT_DOWN); + private final KeyCodeCombination KEY_CODE_CTRL_DELETE = new KeyCodeCombination(KeyCode.DELETE, KeyCombination.SHORTCUT_DOWN); /** * Puts the text into clipboard. @@ -56,17 +57,24 @@ public void putString(String text) { /** * When ever KeyEvent.KEY_PRESSED with 'Ctrl x' or 'Ctrl c' happens the passed string obj is copied in * to the clipboard - * @param event key event + * + * @param event key event * @param getSelectionObjectCallBack + * @param readOnly */ - public void handleCopyCutKeyEvent(KeyEvent event, Callback getSelectionObjectCallBack) { + public void handleCopyCutKeyEvent(KeyEvent event, Callback getSelectionObjectCallBack, boolean readOnly) { if (event.getEventType().getName().equals("KEY_PRESSED") && KEY_CODE_CTRL_X.match(event) - || (KEY_CODE_CTRL_C.match(event)) - || (KEY_CODE_CTRL_INSERT.match(event))) { + || KEY_CODE_CTRL_C.match(event) + || KEY_CODE_CTRL_DELETE.match(event) + || KEY_CODE_CTRL_INSERT.match(event)) { Object obj = getSelectionObjectCallBack.call(null); String selectedText = String.valueOf(obj); - if (selectedText.isEmpty()) { + if ((readOnly && KEY_CODE_CTRL_X.match(event)) + || (readOnly && KEY_CODE_CTRL_INSERT.match(event)) + || (readOnly && KEY_CODE_CTRL_DELETE.match(event)) + || selectedText.isEmpty() + ) { event.consume(); } else { Platform.runLater(() -> { diff --git a/src/main/resources/eu/mihosoft/monacofx/monaco-editor/index.html b/src/main/resources/eu/mihosoft/monacofx/monaco-editor/index.html index 8668a9c..7a858b0 100644 --- a/src/main/resources/eu/mihosoft/monacofx/monaco-editor/index.html +++ b/src/main/resources/eu/mihosoft/monacofx/monaco-editor/index.html @@ -94,6 +94,8 @@ } ]); + const model = editorView.getModel(); + model.setEOL(monaco.editor.EndOfLineSequence.LF); editorView.addAction({ id: 'editor.action.clipboardCopyAction',