Merge "Async load popular modes into CodeMirror"
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 14008e6..c8f40b2 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.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 @@
initHostname();
Window.setTitle(M.windowTitle1(myHost));
- CodeMirror.install();
final HostPageDataService hpd = GWT.create(HostPageDataService.class);
hpd.load(new GerritCallback<HostPageData>() {
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 d0398daa..af149ab 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 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 @@
protected void onLoad() {
super.onLoad();
+ CallbackGroup group = new CallbackGroup();
+ CodeMirror.initLibrary(group.add(new GerritCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ }
+ }));
DiffApi.diff(revision, path)
.base(base)
.wholeFile()
.ignoreWhitespace(DiffApi.IgnoreWhitespace.NONE)
- .get(new ScreenLoadCallback<DiffInfo>(this) {
+ .get(group.add(new GerritCallback<DiffInfo>() {
@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<Void>(CodeMirrorDemo.this){
+ @Override
+ protected void preDisplay(Void result) {
+ display(diff);
+ }
+ });
}
- });
+ }));
}
@Override
@@ -92,10 +109,8 @@
.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 @@
}
});
}
+
+ 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 20e413c..64ac16d 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 @@
<inherits name='com.google.gwt.logging.Logging'/>
<inherits name='com.google.gwt.resources.Resources'/>
<source path='lib'/>
+ <source path='mode'/>
</module>
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 e365595..11d10df 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 @@
* @link http://codemirror.net/doc/manual.html#api
*/
public class CodeMirror extends JavaScriptObject {
+ public static void initLibrary(AsyncCallback<Void> cb) {
+ Loader.initLibrary(cb);
+ }
+
public static native CodeMirror create(Element parent, Configuration cfg) /*-{
return $wnd.CodeMirror(parent, cfg);
}-*/;
@@ -48,42 +42,6 @@
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<TextResource>() {
- @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 0000000..0637a1b
--- /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<Void> 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<TextResource>() {
+ @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<Void> callback) {
+ final ScriptElement[] script = new ScriptElement[1];
+ script[0] = ScriptInjector.fromUrl(js.asString())
+ .setWindow(ScriptInjector.TOP_WINDOW)
+ .setCallback(new Callback<Void, Exception>() {
+ @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 0000000..86b3776
--- /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<String, String> mimeAlias;
+
+ /** Map of content type "text/x-java" to mode name "clike". */
+ private static final Map<String, String> mimeModes;
+
+ /** Map of names such as "clike" to URI for code download. */
+ private static final Map<String, SafeUri> 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<String, String>();
+ mimeModes = new HashMap<String, String>();
+ modeUris = new HashMap<String, SafeUri>();
+
+ 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<String> loading = new HashSet<String>(4);
+ private int pending;
+ private AsyncCallback<Void> 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<Void> 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<Void>() {
+ @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 0000000..e2d3e3c
--- /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 0000000..bcb615a
--- /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 cdb16cd..7df9fec 100644
--- a/lib/LICENSE-codemirror
+++ b/lib/LICENSE-codemirror
@@ -17,3 +17,28 @@
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.