/******************************************************************************* * Copyright (c) 2014-2015 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.editor.codemirror.client; import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.ide.api.extension.Extension; import org.eclipse.che.ide.api.notification.Notification; import org.eclipse.che.ide.api.notification.Notification.Type; import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.editor.codemirror.base.client.BaseCodemirrorInitializer; import org.eclipse.che.ide.editor.codemirror.base.client.BaseCodemirrorPromise; import org.eclipse.che.ide.editor.codemirror.resources.client.BasePathConstant; import org.eclipse.che.ide.editor.codemirrorjso.client.CodeMirrorOverlay; import org.eclipse.che.ide.jseditor.client.codeassist.CompletionResources; import org.eclipse.che.ide.jseditor.client.defaulteditor.EditorBuilder; import org.eclipse.che.ide.jseditor.client.editorconfig.DefaultTextEditorConfiguration; import org.eclipse.che.ide.jseditor.client.editortype.EditorType; import org.eclipse.che.ide.jseditor.client.editortype.EditorTypeRegistry; import org.eclipse.che.ide.jseditor.client.requirejs.RequireJsLoader; import org.eclipse.che.ide.jseditor.client.requirejs.RequirejsErrorHandler.RequireError; import org.eclipse.che.ide.jseditor.client.texteditor.AbstractEditorModule.EditorInitializer; import org.eclipse.che.ide.jseditor.client.texteditor.AbstractEditorModule.InitializerCallback; import org.eclipse.che.ide.jseditor.client.texteditor.ConfigurableTextEditor; import org.eclipse.che.ide.jseditor.client.texteditor.EmbeddedTextEditorPresenter; import org.eclipse.che.ide.util.loging.Log; import com.google.gwt.core.client.Callback; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptException; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.client.RunAsyncCallback; import com.google.inject.Inject; @Extension(title = "CodeMirror Editor", version = "1.1.0") public class CodeMirrorEditorExtension { /** The logger. */ private static final Logger LOG = Logger.getLogger(CodeMirrorEditorExtension.class.getSimpleName()); /** The editor type key. */ public static final String CODEMIRROR_EDITOR_KEY = "codemirror"; /** The codemirror javascript module key. */ public static final String CODEMIRROR_MODULE_KEY = "CodeMirror"; /** The base path for codemirror resources. */ private final String codemirrorBase; private final NotificationManager notificationManager; private final RequireJsLoader requireJsLoader; private final EditorTypeRegistry editorTypeRegistry; private final CodeMirrorEditorModule editorModule; private final CodeMirrorTextEditorFactory codeMirrorTextEditorFactory; private boolean initFailedWarnedOnce = false; @Inject public CodeMirrorEditorExtension(final EditorTypeRegistry editorTypeRegistry, final RequireJsLoader requireJsLoader, final NotificationManager notificationManager, final CodeMirrorEditorModule editorModule, final BaseCodemirrorPromise basePromise, final BaseCodemirrorInitializer baseInitializer, final CodeMirrorTextEditorFactory codeMirrorTextEditorFactory, final CompletionResources completionResources, final BasePathConstant basePathConstant) { this.notificationManager = notificationManager; this.requireJsLoader = requireJsLoader; this.editorModule = editorModule; this.editorTypeRegistry = editorTypeRegistry; this.codeMirrorTextEditorFactory = codeMirrorTextEditorFactory; this.codemirrorBase = basePathConstant.basePath(); completionResources.completionCss().ensureInjected(); Log.debug(CodeMirrorEditorExtension.class, "Codemirror extension module=" + editorModule); editorModule.setEditorInitializer(new EditorInitializer() { @Override public void initialize(final InitializerCallback callback) { // add code-splitting of the whole orion editor GWT.runAsync(new RunAsyncCallback() { @Override public void onSuccess() { initBaseCodeMirror(basePromise, baseInitializer, callback); } @Override public void onFailure(final Throwable reason) { callback.onFailure(reason); } }); } }); // must not delay registerEditor(); CodeMirrorKeymaps.init(); } private void initBaseCodeMirror(final BaseCodemirrorPromise basePromise, final BaseCodemirrorInitializer baseInitializer, final InitializerCallback callback) { if (basePromise.getPromise() == null) { baseInitializer.init(); } final Promise<CodeMirrorOverlay> editorPromise = basePromise.getPromise().then(new Operation<CodeMirrorOverlay>() { @Override public void apply(final CodeMirrorOverlay codemirror) throws OperationException { setupFullCodeMirror(callback); } }); editorPromise.catchError(new Operation<PromiseError>() { @Override public void apply(final PromiseError arg) throws OperationException { editorModule.setError(); } }); } private void setupFullCodeMirror(final InitializerCallback callback) { /* * This could be simplified and optimized with a all-in-one minified js from http://codemirror.net/doc/compress.html but at least * while debugging, unmodified source is necessary. Another option would be to include all-in-one minified along with a source map */ final String[] scripts = new String[]{ // base script codemirrorBase + "lib/codemirror", // library of modes codemirrorBase + "mode/meta", // mode autoloading codemirrorBase + "addon/mode/loadmode", /* We will preload modes that have extensions */ // language modes codemirrorBase + "mode/xml/xml", codemirrorBase + "mode/htmlmixed/htmlmixed", // must be defined after xml codemirrorBase + "mode/javascript/javascript", codemirrorBase + "mode/coffeescript/coffeescript", codemirrorBase + "mode/css/css", codemirrorBase + "mode/sql/sql", codemirrorBase + "mode/clike/clike", codemirrorBase + "mode/markdown/markdown", codemirrorBase + "mode/gfm/gfm", // markdown extension for github // hints codemirrorBase + "addon/hint/show-hint", codemirrorBase + "addon/hint/xml-hint", codemirrorBase + "addon/hint/html-hint", codemirrorBase + "addon/hint/javascript-hint", codemirrorBase + "addon/hint/css-hint", codemirrorBase + "addon/hint/anyword-hint", codemirrorBase + "addon/hint/sql-hint", // pair matching codemirrorBase + "addon/edit/closebrackets", codemirrorBase + "addon/edit/closetag", codemirrorBase + "addon/edit/matchbrackets", codemirrorBase + "addon/edit/matchtags", // the two following are added to repair actual functionality in 'classic' editor codemirrorBase + "addon/selection/mark-selection", codemirrorBase + "addon/selection/active-line", // for search codemirrorBase + "addon/search/search", codemirrorBase + "addon/dialog/dialog", codemirrorBase + "addon/search/searchcursor", codemirrorBase + "addon/search/match-highlighter", // comment management codemirrorBase + "addon/comment/comment", codemirrorBase + "addon/comment/continuecomment", // folding codemirrorBase + "addon/fold/foldcode", codemirrorBase + "addon/fold/foldgutter", codemirrorBase + "addon/fold/brace-fold", codemirrorBase + "addon/fold/xml-fold", // also required by matchbrackets and closebrackets codemirrorBase + "addon/fold/comment-fold", codemirrorBase + "addon/fold/indent-fold", codemirrorBase + "addon/fold/markdown-fold", codemirrorBase + "addon/scroll/simplescrollbars", codemirrorBase + "addon/scroll/annotatescrollbar", codemirrorBase + "addon/scroll/scrollpastend", codemirrorBase + "addon/search/matchesonscrollbar", }; this.requireJsLoader.require(new Callback<JavaScriptObject[], Throwable>() { @Override public void onSuccess(final JavaScriptObject[] result) { editorModule.setReady(); callback.onSuccess(); } @Override public void onFailure(final Throwable e) { if (e instanceof JavaScriptException) { final JavaScriptException jsException = (JavaScriptException)e; final Object nativeException = jsException.getThrown(); if (nativeException instanceof RequireError) { final RequireError requireError = (RequireError)nativeException; final String errorType = requireError.getRequireType(); String message = "Codemirror injection failed: " + errorType + " "; final JsArrayString modules = requireError.getRequireModules(); if (modules != null) { message += modules.join(","); } Log.debug(CodeMirrorEditorExtension.class, message); } } initializationFailed(callback, "Unable to initialize CodeMirror", e); } }, scripts, new String[]{CODEMIRROR_MODULE_KEY}); } private void registerEditor() { LOG.fine("Registering CodeMirror editor type."); this.editorTypeRegistry.registerEditorType(EditorType.fromKey(CODEMIRROR_EDITOR_KEY), "CodeMirror", new EditorBuilder() { @Override public ConfigurableTextEditor buildEditor() { final EmbeddedTextEditorPresenter<CodeMirrorEditorWidget> editor = codeMirrorTextEditorFactory.createTextEditor(); editor.initialize(new DefaultTextEditorConfiguration(), notificationManager); return editor; } }); } private void initializationFailed(final InitializerCallback callback, final String errorMessage, Throwable e) { if (this.initFailedWarnedOnce) { return; } this.initFailedWarnedOnce = true; this.notificationManager.showNotification(new Notification(errorMessage, Type.ERROR)); this.notificationManager.showNotification(new Notification("CodeMirror editor is not available", Type.WARNING)); LOG.log(Level.SEVERE, errorMessage + " - ", e); callback.onFailure(e); } }