blob: 61d263c9f01817511f4983b0189cdb061cd9d795 [file] [log] [blame]
/*****************************************************************************
* 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);
}
}