diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java index 14008e6cd4..c8f40b26f6 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java @@ -85,8 +85,6 @@ import com.google.gwtjsonrpc.client.impl.ResultDeserializer; import com.google.gwtjsonrpc.common.AsyncCallback; import com.google.gwtorm.client.KeyUtil; -import net.codemirror.lib.CodeMirror; - import java.util.ArrayList; public class Gerrit implements EntryPoint { @@ -368,7 +366,6 @@ public class Gerrit implements EntryPoint { initHostname(); Window.setTitle(M.windowTitle1(myHost)); - CodeMirror.install(); final HostPageDataService hpd = GWT.create(HostPageDataService.class); hpd.load(new GerritCallback() { diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java index d0398daa61..af149abe1a 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java @@ -14,6 +14,8 @@ package com.google.gerrit.client.diff; +import com.google.gerrit.client.rpc.CallbackGroup; +import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.ScreenLoadCallback; import com.google.gerrit.client.ui.Screen; import com.google.gerrit.reviewdb.client.PatchSet; @@ -25,6 +27,7 @@ import com.google.gwt.user.client.ui.FlowPanel; import net.codemirror.lib.CodeMirror; import net.codemirror.lib.Configuration; +import net.codemirror.lib.ModeInjector; public class CodeMirrorDemo extends Screen { private static final int HEADER_FOOTER = 60 + 15 * 2 + 38; @@ -55,16 +58,30 @@ public class CodeMirrorDemo extends Screen { protected void onLoad() { super.onLoad(); + CallbackGroup group = new CallbackGroup(); + CodeMirror.initLibrary(group.add(new GerritCallback() { + @Override + public void onSuccess(Void result) { + } + })); DiffApi.diff(revision, path) .base(base) .wholeFile() .ignoreWhitespace(DiffApi.IgnoreWhitespace.NONE) - .get(new ScreenLoadCallback(this) { + .get(group.add(new GerritCallback() { @Override - protected void preDisplay(DiffInfo diff) { - display(diff); + public void onSuccess(final DiffInfo diff) { + new ModeInjector() + .add(getContentType(diff.meta_a())) + .add(getContentType(diff.meta_b())) + .inject(new ScreenLoadCallback(CodeMirrorDemo.this){ + @Override + protected void preDisplay(Void result) { + display(diff); + } + }); } - }); + })); } @Override @@ -92,10 +109,8 @@ public class CodeMirrorDemo extends Screen { .set("readOnly", true) .set("lineNumbers", true) .set("tabSize", 2) + .set("mode", getContentType(diff.meta_b())) .set("value", diff.text_b()); - if (diff.meta_b() != null && diff.meta_b().content_type() != null) { - cfg.set("mode", diff.meta_b().content_type()); - } cm = CodeMirror.create(editorContainer.getElement(), cfg); cm.setWidth("100%"); @@ -108,4 +123,10 @@ public class CodeMirrorDemo extends Screen { } }); } + + private static String getContentType(DiffInfo.FileMeta meta) { + return meta != null && meta.content_type() != null + ? ModeInjector.getContentType(meta.content_type()) + : null; + } } diff --git a/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml b/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml index 20e413c97a..64ac16d79e 100644 --- a/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml +++ b/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml @@ -17,4 +17,5 @@ + diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java index e365595429..11d10dfeca 100644 --- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java +++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java @@ -15,18 +15,8 @@ package net.codemirror.lib; import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.ScriptElement; -import com.google.gwt.dom.client.StyleInjector; -import com.google.gwt.resources.client.ExternalTextResource; -import com.google.gwt.resources.client.ResourceCallback; -import com.google.gwt.resources.client.ResourceException; -import com.google.gwt.resources.client.TextResource; -import com.google.gwt.safehtml.shared.SafeUri; - -import java.util.logging.Level; -import java.util.logging.Logger; +import com.google.gwt.user.client.rpc.AsyncCallback; /** * Glue to connect CodeMirror to be callable from GWT. @@ -34,6 +24,10 @@ import java.util.logging.Logger; * @link http://codemirror.net/doc/manual.html#api */ public class CodeMirror extends JavaScriptObject { + public static void initLibrary(AsyncCallback cb) { + Loader.initLibrary(cb); + } + public static native CodeMirror create(Element parent, Configuration cfg) /*-{ return $wnd.CodeMirror(parent, cfg); }-*/; @@ -48,42 +42,6 @@ public class CodeMirror extends JavaScriptObject { public final native void refresh() /*-{ this.refresh(); }-*/; public final native Element getWrapperElement() /*-{ return this.getWrapperElement(); }-*/; - public static void install() { - asyncInjectCss(Lib.I.css()); - asyncInjectScript(Lib.I.js().getSafeUri()); - } - - private static void asyncInjectCss(ExternalTextResource css) { - try { - css.getText(new ResourceCallback() { - @Override - public void onSuccess(TextResource resource) { - StyleInjector.inject(resource.getText()); - } - - @Override - public void onError(ResourceException e) { - error(e); - } - }); - } catch (ResourceException e) { - error(e); - } - } - - private static void asyncInjectScript(SafeUri uri) { - ScriptElement script = Document.get().createScriptElement(); - script.setSrc(uri.asString()); - script.setLang("javascript"); - script.setType("text/javascript"); - Document.get().getBody().appendChild(script); - } - - private static void error(ResourceException e) { - Logger log = Logger.getLogger("net.codemirror"); - log.log(Level.SEVERE, "Cannot fetch CSS", e); - } - protected CodeMirror() { } } diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java new file mode 100644 index 0000000000..0637a1b1da --- /dev/null +++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java @@ -0,0 +1,90 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package net.codemirror.lib; + +import com.google.gwt.core.client.Callback; +import com.google.gwt.core.client.ScriptInjector; +import com.google.gwt.dom.client.ScriptElement; +import com.google.gwt.dom.client.StyleInjector; +import com.google.gwt.resources.client.ExternalTextResource; +import com.google.gwt.resources.client.ResourceCallback; +import com.google.gwt.resources.client.ResourceException; +import com.google.gwt.resources.client.TextResource; +import com.google.gwt.safehtml.shared.SafeUri; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import java.util.logging.Level; +import java.util.logging.Logger; + +class Loader { + private static native boolean isLibLoaded() + /*-{ return $wnd.hasOwnProperty('CodeMirror'); }-*/; + + static void initLibrary(AsyncCallback cb) { + if (isLibLoaded()) { + cb.onSuccess(null); + } else { + injectCss(Lib.I.css()); + injectScript(Lib.I.js().getSafeUri(), cb); + } + } + + private static void injectCss(ExternalTextResource css) { + try { + css.getText(new ResourceCallback() { + @Override + public void onSuccess(TextResource resource) { + StyleInjector.inject(resource.getText()); + } + + @Override + public void onError(ResourceException e) { + error(e); + } + }); + } catch (ResourceException e) { + error(e); + } + } + + static void injectScript(SafeUri js, final AsyncCallback callback) { + final ScriptElement[] script = new ScriptElement[1]; + script[0] = ScriptInjector.fromUrl(js.asString()) + .setWindow(ScriptInjector.TOP_WINDOW) + .setCallback(new Callback() { + @Override + public void onSuccess(Void result) { + script[0].removeFromParent(); + callback.onSuccess(result); + } + + @Override + public void onFailure(Exception reason) { + error(reason); + callback.onFailure(reason); + } + }) + .inject() + .cast(); + } + + private static void error(Exception e) { + Logger log = Logger.getLogger("net.codemirror"); + log.log(Level.SEVERE, "Cannot load portions of CodeMirror", e); + } + + private Loader() { + } +} diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java new file mode 100644 index 0000000000..86b37764d0 --- /dev/null +++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/ModeInjector.java @@ -0,0 +1,179 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package net.codemirror.lib; + +import com.google.gwt.core.client.JsArrayString; +import com.google.gwt.resources.client.DataResource; +import com.google.gwt.safehtml.shared.SafeUri; +import com.google.gwt.user.client.rpc.AsyncCallback; + +import net.codemirror.mode.Modes; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ModeInjector { + /** Map of server content type to CodeMiror mode or content type. */ + private static final Map mimeAlias; + + /** Map of content type "text/x-java" to mode name "clike". */ + private static final Map mimeModes; + + /** Map of names such as "clike" to URI for code download. */ + private static final Map modeUris; + + static { + DataResource[] all = { + Modes.I.clike(), + Modes.I.css(), + Modes.I.go(), + Modes.I.htmlmixed(), + Modes.I.javascript(), + Modes.I.properties(), + Modes.I.python(), + Modes.I.shell(), + Modes.I.sql(), + Modes.I.velocity(), + Modes.I.xml(), + }; + + mimeAlias = new HashMap(); + mimeModes = new HashMap(); + modeUris = new HashMap(); + + for (DataResource m : all) { + modeUris.put(m.getName(), m.getSafeUri()); + } + parseModeMap(); + } + + private static void parseModeMap() { + String mode = null; + for (String line : Modes.I.mode_map().getText().split("\n")) { + int eq = line.indexOf('='); + if (0 < eq) { + mimeAlias.put( + line.substring(0, eq).trim(), + line.substring(eq + 1).trim()); + } else if (line.endsWith(":")) { + String n = line.substring(0, line.length() - 1); + if (modeUris.containsKey(n)) { + mode = n; + } + } else if (mode != null && line.contains("/")) { + mimeModes.put(line, mode); + } else { + mode = null; + } + } + } + + public static String getContentType(String mode) { + String real = mode != null ? mimeAlias.get(mode) : null; + return real != null ? real : mode; + } + + private static native boolean isModeLoaded(String n) + /*-{ return $wnd.CodeMirror.modes.hasOwnProperty(n); }-*/; + + private static native boolean isMimeLoaded(String n) + /*-{ return $wnd.CodeMirror.mimeModes.hasOwnProperty(n); }-*/; + + private static native JsArrayString getDependencies(String n) + /*-{ return $wnd.CodeMirror.modes[n].dependencies || []; }-*/; + + private final Set loading = new HashSet(4); + private int pending; + private AsyncCallback appCallback; + + public ModeInjector add(String name) { + if (name == null || isModeLoaded(name) || isMimeLoaded(name)) { + return this; + } + + String mode = mimeModes.get(name); + if (mode == null) { + mode = name; + } + + SafeUri uri = modeUris.get(mode); + if (uri == null) { + Logger.getLogger("net.codemirror").log( + Level.WARNING, + "CodeMirror mode " + mode + " not configured."); + return this; + } + + loading.add(mode); + return this; + } + + public void inject(AsyncCallback appCallback) { + this.appCallback = appCallback; + for (String mode : loading) { + beginLoading(mode); + } + if (pending == 0) { + appCallback.onSuccess(null); + } + } + + private void beginLoading(final String mode) { + pending++; + Loader.injectScript( + modeUris.get(mode), + new AsyncCallback() { + @Override + public void onSuccess(Void result) { + pending--; + ensureDependenciesAreLoaded(mode); + if (pending == 0) { + appCallback.onSuccess(null); + } + } + + @Override + public void onFailure(Throwable caught) { + if (--pending == 0) { + appCallback.onFailure(caught); + } + } + }); + } + + private void ensureDependenciesAreLoaded(String mode) { + JsArrayString deps = getDependencies(mode); + for (int i = 0; i < deps.length(); i++) { + String d = deps.get(i); + if (loading.contains(d) || isModeLoaded(d)) { + continue; + } + + SafeUri uri = modeUris.get(d); + if (uri == null) { + Logger.getLogger("net.codemirror").log( + Level.SEVERE, + "CodeMirror mode " + mode + " needs " + d); + continue; + } + + beginLoading(d); + } + } +} diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java new file mode 100644 index 0000000000..e2d3e3c5fc --- /dev/null +++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java @@ -0,0 +1,40 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package net.codemirror.mode; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.DataResource; +import com.google.gwt.resources.client.DataResource.DoNotEmbed; +import com.google.gwt.resources.client.TextResource; + +public interface Modes extends ClientBundle { + public static final Modes I = GWT.create(Modes.class); + + @Source("mode_map") TextResource mode_map(); + @Source("clike/clike.js") @DoNotEmbed DataResource clike(); + @Source("css/css.js") @DoNotEmbed DataResource css(); + @Source("go/go.js") @DoNotEmbed DataResource go(); + @Source("htmlmixed/htmlmixed.js") @DoNotEmbed DataResource htmlmixed(); + @Source("javascript/javascript.js") @DoNotEmbed DataResource javascript(); + @Source("properties/properties.js") @DoNotEmbed DataResource properties(); + @Source("python/python.js") @DoNotEmbed DataResource python(); + @Source("shell/shell.js") @DoNotEmbed DataResource shell(); + @Source("sql/sql.js") @DoNotEmbed DataResource sql(); + @Source("velocity/velocity.js") @DoNotEmbed DataResource velocity(); + @Source("xml/xml.js") @DoNotEmbed DataResource xml(); + + // When adding a resource, update static initializer in ModeInjector. +} diff --git a/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map b/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map new file mode 100644 index 0000000000..bcb615ad47 --- /dev/null +++ b/gerrit-gwtui/src/main/java/net/codemirror/mode/mode_map @@ -0,0 +1,54 @@ +clike: +text/x-csrc +text/x-c +text/x-chdr +text/x-c++src +text/x-c++hdr +text/x-java +text/x-csharp +text/x-scala + +css: +text/css +text/x-scss + +go: +text/x-go + +htmlmixed: +text/html + +javascript: +text/javascript +text/ecmascript +application/javascript +application/ecmascript +application/json +application/x-json +text/typescript +application/typescript + +properties: +text/x-ini +text/x-properties + +python: +text/x-python + +shell: +text/x-sh + +sql: +text/x-sql +text/x-mariadb +text/x-mysql +text/x-plsql + +velocity: +text/velocity + +xml: +text/xml +application/xml + +text/x-java-source = text/x-java diff --git a/lib/LICENSE-codemirror b/lib/LICENSE-codemirror index cdb16cde3d..7df9fec3dc 100644 --- a/lib/LICENSE-codemirror +++ b/lib/LICENSE-codemirror @@ -17,3 +17,28 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---- + +codemirror Python mode: +---- +The MIT License + +Copyright (c) 2010 Timothy Farrell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.