| /***************************************************************************** |
| * Copyright (c) 2018 CEA LIST |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * David Lopez david.lopez@cea.fr(CEA LIST) |
| * |
| *****************************************************************************/ |
| package org.eclipse.papyrus.aceeditor; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.papyrus.infra.widgets.editors.AbstractValueEditor; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.widgets.Composite; |
| |
| import javafx.application.Platform; |
| import javafx.beans.value.ChangeListener; |
| import javafx.beans.value.ObservableValue; |
| import javafx.concurrent.Worker; |
| import javafx.embed.swt.FXCanvas; |
| import javafx.scene.Scene; |
| import javafx.scene.web.WebView; |
| import netscape.javascript.JSObject; |
| |
| public class AceCodeEditor extends AbstractValueEditor{ |
| |
| private WebView webView; |
| private boolean webEngineIsReady; |
| private String editedCode; |
| private JSObject window; |
| |
| private AceCodeEditorConfiguration configuration = new AceCodeEditorConfiguration(); |
| private List<CodeAnnotation> annotations = new ArrayList<>(); |
| private List<AceCodeEditorListener> listeners = new ArrayList<AceCodeEditorListener>(); |
| |
| private void buildEditor(int style) { |
| final Composite container = new Composite(this, style); |
| |
| GridData myEditorData = new GridData(SWT.BEGINNING, SWT.BEGINNING, true, true); |
| myEditorData.widthHint = 600; |
| myEditorData.grabExcessVerticalSpace = true; |
| myEditorData.verticalAlignment = SWT.FILL; |
| |
| final FXCanvas fxCanvas = new FXCanvas(container, SWT.NONE){ |
| public Point computeSize(int wHint, int hHint, boolean changed) { |
| getScene().getWindow().sizeToScene(); |
| // int width = (int) getScene().getWidth(); |
| // int height = (int) getScene().getHeight(); |
| // return new Point(width, height); |
| return super.computeSize(wHint, hHint, changed); |
| } |
| }; |
| fxCanvas.setSize(800, 200); |
| |
| editedCode = ""; |
| webView = new WebView(); |
| webEngineIsReady = false; |
| webView.getEngine().setJavaScriptEnabled(true); |
| webView.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() { |
| |
| @Override |
| public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, |
| Worker.State newValue) { |
| |
| if( newValue == Worker.State.SUCCEEDED ) { |
| onHTMLLoaded(); |
| } |
| |
| } |
| }); |
| |
| refresh(); |
| fxCanvas.setScene(new Scene(webView)); |
| setCommitOnFocusLost(fxCanvas); |
| } |
| |
| protected void onHTMLLoaded() { |
| webEngineIsReady = true; |
| |
| //window = (JSObject) webView.getEngine().executeScript("window"); |
| |
| window = executeScript("window"); |
| window.setMember("editorListener", AceCodeEditor.this); |
| |
| pushStateToJS(); |
| } |
| |
| protected <T> T executeScript(String script) { |
| if( !webEngineIsReady ) |
| return null; |
| |
| // Platform.runLater(new Runnable() { |
| // @Override public void run() { |
| // //group.getItems.add(i); |
| // (T) webView.getEngine().executeScript(script); |
| // } |
| // }); |
| |
| return (T) webView.getEngine().executeScript(script); |
| } |
| |
| public void runInFXThread(Runnable runnable) { |
| Platform.runLater(runnable); |
| } |
| |
| protected AceCodeEditor(Composite parent, int style) { |
| super(parent); |
| |
| //This fixes a bug in jdk9.0 bug |
| Platform.setImplicitExit(false); |
| |
| buildEditor(style); |
| } |
| |
| private void pushStateToJS() { |
| pushValueToJS(); |
| pushAnnotationsToJS(); |
| } |
| |
| private void pushAnnotationsToJS() { |
| if( !webEngineIsReady ) |
| return; |
| |
| StringBuffer call = new StringBuffer(); |
| call.append("editor.getSession().setAnnotations(["); |
| for( int i = 0; i < annotations.size(); i++ ) { |
| CodeAnnotation a = annotations.get(i); |
| call.append(a.toJSLine()); |
| if( i <= annotations.size() - 1 ) |
| call.append(','); |
| } |
| call.append("]);"); |
| |
| //webView.getEngine().executeScript(call.toString()); |
| executeScript(call.toString()); |
| } |
| |
| private void pushValueToJS() { |
| if( !webEngineIsReady ) |
| return; |
| |
| // String fixedcontent = editedCode.replace("'", "\\'"); |
| // fixedcontent = fixedcontent.replace(System.getProperty("line.separator"), "\\n"); |
| // fixedcontent = fixedcontent.replace("\n", "\\n"); |
| // fixedcontent = fixedcontent.replace("\r", "\\n"); |
| //executeScript("editor.setValue('" + fixedcontent + "')"); |
| // System.out.println("pushing value to JS"); |
| executeScript("editor.setValue(String.raw`" + editedCode + "`)"); |
| } |
| |
| public static String cleanString(String dirty) { |
| return dirty |
| .replace("'", "\\'") |
| .replace("\"", "\\\"") |
| .replace(System.getProperty("line.separator"), "\\n") |
| .replace("\n", "\\n") |
| .replace("\r", "\\n") |
| ; |
| } |
| public void setValue(String editedCode) { |
| this.editedCode = editedCode; |
| pushStateToJS(); |
| } |
| |
| private String pullValueFromJS() { |
| if( webEngineIsReady ) |
| editedCode = executeScript("editor.getValue()"); |
| //editedCode = (String) webView.getEngine().executeScript("editor.getValue()"); |
| return editedCode; |
| } |
| |
| @Override |
| public Object getValue() { |
| return pullValueFromJS(); |
| } |
| |
| @Override |
| public Object getEditableType() { |
| return String.class; |
| } |
| |
| @Override |
| public void setReadOnly(boolean readOnly) { |
| |
| } |
| |
| @Override |
| public boolean isReadOnly() { |
| return false; |
| } |
| |
| public void refresh() { |
| //detect if config changed, etc... |
| String html = generateMainHtml(); |
| webView.getEngine().loadContent(html); |
| } |
| |
| protected String generateMainHtml() { |
| StringBuffer buf = new StringBuffer(); |
| |
| String NL = "\n"; |
| |
| buf |
| .append("<!DOCTYPE html>").append(NL) |
| .append("<html lang=\"en\">").append(NL) |
| .append("<head>").append(NL) |
| .append("<title>ACE in Action</title>").append(NL) |
| .append("<style type=\"text/css\" media=\"screen\">").append(NL) |
| .append(" #editor {").append(NL) |
| .append(" position: absolute;").append(NL) |
| .append(" top: 0;").append(NL) |
| .append(" right: 0;").append(NL) |
| .append(" bottom: 0;").append(NL) |
| .append(" left: 0;").append(NL) |
| .append(" }").append(NL) |
| .append("</style>").append(NL) |
| .append("</head>").append(NL) |
| .append("<body>").append(NL) |
| .append("<div id=\"editor\">").append(NL) |
| .append("</div>").append(NL) |
| // .append("<script src=\"https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.1/ace.js" + "\" type=\"text/javascript\" charset=\"utf-8\"></script>").append(NL) |
| .append("<script src=\""+ AceCodeEditorConfiguration.ACE_BASE + "\" type=\"text/javascript\" charset=\"utf-8\"></script>").append(NL) |
| .append("<script language=\"javascript\">").append(NL) |
| .append(" var editor = ace.edit(\"editor\");").append(NL) |
| .append(" editor.setTheme(\"ace/theme/"+ configuration.getTheme() +"\");").append(NL) |
| .append(" editor.setOptions({displayIndentGuides: true, showFoldWidgets: false});").append(NL) |
| .append(" editor.session.setMode(\"ace/mode/"+ configuration.getLanguage() +"\");").append(NL) |
| |
| // .append("editor.commands.addCommand({\r\n" + |
| // "name: 'saveFile',\r\n" + |
| // "bindKey: {\r\n" + |
| // "win: 'Ctrl-S',\r\n" + |
| // "mac: 'Command-S',\r\n" + |
| // "sender: 'editor|cli'\r\n" + |
| // "},\r\n" + |
| // "exec: function(env, args, request) {\r\n" + |
| // "alert(\"HI!\");\r\n" + |
| // "}\r\n" + |
| // "});").append(NL) |
| |
| // .append(" editor.commands.addCommand({\"").append(NL) |
| // .append(" name: 'save',").append(NL) |
| // .append(" bindKey: {win: 'Ctrl-S', 'mac': 'Cmd-S'},").append(NL) |
| // .append(" exec: function(editor) {").append(NL) |
| // .append(" alert('saving')").append(NL) |
| // .append(" }").append(NL) |
| // .append(" })").append(NL) |
| |
| |
| .append(" editor.session.setMode(\"ace/mode/"+ configuration.getLanguage() +"\");").append(NL) |
| .append(" editor.session.on('change', function(e){ if( editor.session.getUndoManager().hasUndo() ) editorListener.onSessionChange(editor.getValue()) });").append(NL) |
| //.append(" editor.setOptions({enableBasicAutocompletion: true, enableLiveAutocompletion: true});").append(NL) |
| |
| // THE PYTHON MODE MIGHT BE BROKEN. |
| // |
| // WRITING 'FROM' ALREADY BREAKS THE EDITOR |
| |
| .append("</script>").append(NL) |
| .append("</body>").append(NL) |
| .append("</html>").append(NL); |
| |
| return buf.toString(); |
| } |
| |
| public AceCodeEditorConfiguration getConfiguration() { |
| return configuration; |
| } |
| |
| public void setAnnotations(List<CodeAnnotation> annotations) { |
| this.annotations.clear(); |
| this.annotations.addAll(annotations); |
| pushAnnotationsToJS(); |
| } |
| |
| public void onSessionChange(String newValue) { |
| if( isInitialChange(newValue) ) |
| return; |
| |
| listeners.forEach( l -> l.onChange(newValue) ); |
| } |
| |
| public boolean isInitialChange(String value) { |
| if( value == null || value.isEmpty() ) |
| return true; |
| |
| return value.equals(editedCode); |
| } |
| |
| public void markClean() { |
| // if( webEngineIsReady ) { |
| // webView.getEngine().executeScript("editor.session.getUndoManager().markClean()"); |
| // } |
| executeScript("editor.session.getUndoManager().markClean()"); |
| } |
| |
| public void addAceCodeEditorListener(AceCodeEditorListener listener) { |
| listeners.add(listener); |
| } |
| } |