Starting to implement side-by-side diff.

Including the mark-selection addon from CodeMirror.
Adding more API wrappers to CodeMirror to support line formatting.
Adding line coloring and padding for inserts and deletes to CodeMirrorDemo.

Change-Id: I0a96506166de8e9dc32241b21d99cce9a3890fad
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 66abe88..70b9ff9 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,19 +14,24 @@
 
 package com.google.gerrit.client.diff;
 
+import com.google.gerrit.client.diff.DiffInfo.Region;
 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;
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.logical.shared.ResizeEvent;
 import com.google.gwt.event.logical.shared.ResizeHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Window;
 
 import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.CodeMirror.LineClassWhere;
 import net.codemirror.lib.Configuration;
+import net.codemirror.lib.LineCharacter;
 import net.codemirror.lib.ModeInjector;
 
 public class CodeMirrorDemo extends Screen {
@@ -116,6 +121,7 @@
   private void display(DiffInfo diff) {
     cmA = displaySide(diff.meta_a(), diff.text_a(), diffTable.getCmA());
     cmB = displaySide(diff.meta_b(), diff.text_b(), diffTable.getCmB());
+    render(diff);
     resizeHandler = Window.addResizeHandler(new ResizeHandler() {
       @Override
       public void onResize(ResizeEvent event) {
@@ -141,13 +147,65 @@
       .set("lineNumbers", true)
       .set("tabSize", 2)
       .set("mode", getContentType(meta))
-      .set("value", contents);
+      .set("value", contents)
+      .setInfinity("viewportMargin");
     final CodeMirror cm = CodeMirror.create(ele, cfg);
     cm.setWidth("100%");
     cm.setHeight(Window.getClientHeight() - HEADER_FOOTER);
     return cm;
   }
 
+  private void addPadding(CodeMirror cm, int line) {
+    Element div = DOM.createDiv();
+    div.setClassName(diffTable.style.padding());
+    cm.addLineWidget(line, div, null);
+  }
+
+  private void render(DiffInfo diff) {
+    JsArray<Region> regions = diff.content();
+    Configuration insertOpt = Configuration.create()
+        .set("className", diffTable.style.insert())
+        .set("readOnly", true);
+    Configuration deleteOpt = Configuration.create()
+        .set("className", diffTable.style.delete())
+        .set("readOnly", true);
+    int lineA = 0, lineB = 0;
+    for (int i = 0; i < regions.length(); i++) {
+      Region current = regions.get(i);
+      if (current.ab() != null) {
+        lineA += current.ab().length();
+        lineB += current.ab().length();
+      } else if (current.a() == null && current.b() != null) {
+        int delta = current.b().length();
+        for (int j = 0; j < delta; j++) {
+          addPadding(cmA, lineA - 1);
+        }
+        for (int j = 0; j < delta; j++) {
+          cmB.addLineClass(lineB, LineClassWhere.WRAP,
+              diffTable.style.insert());
+          LineCharacter from = LineCharacter.create(lineB, 0);
+          cmB.markText(from, from, insertOpt);
+          lineB++;
+        }
+      } else if (current.a() != null && current.b() == null) {
+        int delta = current.a().length();
+        for (int j = 0; j < delta; j++) {
+          addPadding(cmB, lineB - 1);
+        }
+        for (int j = 0; j < delta; j++) {
+          cmA.addLineClass(lineA, LineClassWhere.WRAP,
+              diffTable.style.delete());
+          LineCharacter from = LineCharacter.create(lineA, 0);
+          cmA.markText(from, from, deleteOpt);
+          lineA++;
+        }
+      } else { // TODO: Handle intraline edit.
+        lineA += current.a().length();
+        lineB += current.a().length();
+      }
+    }
+  }
+
   private static String getContentType(DiffInfo.FileMeta meta) {
     return meta != null && meta.content_type() != null
         ? ModeInjector.getContentType(meta.content_type())
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
index bb223e1..e681630 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
@@ -16,6 +16,7 @@
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.Composite;
@@ -29,12 +30,22 @@
   interface Binder extends UiBinder<HTMLPanel, DiffTable> {}
   private static Binder uiBinder = GWT.create(Binder.class);
 
+  interface LineStyle extends CssResource {
+    String insert();
+    String delete();
+    String intraline();
+    String padding();
+  }
+
   @UiField
   DivElement cmA;
 
   @UiField
   DivElement cmB;
 
+  @UiField
+  LineStyle style;
+
   DiffTable() {
     initWidget(uiBinder.createAndBindUi(this));
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
index 735a0e6..5e4cf0d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
@@ -16,7 +16,49 @@
 -->
 <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'>
-  <g:HTMLPanel>
+  <ui:style field='css'>
+    @external .CodeMirror, .CodeMirror-selectedtext, .CodeMirror-scroll;
+
+    .difftable .CodeMirror {
+      border: 1px solid #eee;
+      height: auto;
+    }
+    .difftable .CodeMirror pre {
+      padding: 0;
+      overflow: hidden;
+    }
+    .difftable .CodeMirror-selectedtext.intraline,
+    .difftable .CodeMirror-selectedtext.insert {
+      opacity: 0.5;
+    }
+    .difftable .CodeMirror-scroll {
+      overflow-x: hidden;
+      overflow-y: hidden;
+    }
+    <!--.difftable .CodeMirror-vscrollbar {
+      display: none !important;
+    }-->
+  </ui:style>
+  <ui:style type='com.google.gerrit.client.diff.DiffTable.LineStyle'>
+    @external .CodeMirror-linenumber;
+
+    .insert,
+    .insert .CodeMirror-linenumber {
+      background-color: #dfd;
+    }
+    .delete,
+    .delete .CodeMirror-linenumber {
+      background-color: #fee;
+    }
+    .intraline {
+      background-color: #9f9;
+    }
+    .padding {
+      background-color: #eee;
+      height: 1em;
+    }
+  </ui:style>
+  <g:HTMLPanel styleName='{css.difftable}'>
     <table>
       <tr>
         <td><div ui:field='cmA'></div></td>
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 64ac16d..a3641a553 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml
+++ b/gerrit-gwtui/src/main/java/net/codemirror/CodeMirror.gwt.xml
@@ -16,6 +16,7 @@
 <module>
   <inherits name='com.google.gwt.logging.Logging'/>
   <inherits name='com.google.gwt.resources.Resources'/>
+  <source path='addon'/>
   <source path='lib'/>
   <source path='mode'/>
 </module>
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java b/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java
new file mode 100644
index 0000000..1cddc7f
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java
@@ -0,0 +1,28 @@
+// 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.addon;
+
+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;
+
+public interface Addons extends ClientBundle {
+  public static final Addons I = GWT.create(Addons.class);
+
+  @Source("selection/mark-selection.js")
+  @DoNotEmbed
+  DataResource mark_selection();
+}
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 11d10df..42664b7 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -28,7 +28,8 @@
     Loader.initLibrary(cb);
   }
 
-  public static native CodeMirror create(Element parent, Configuration cfg) /*-{
+  public static native CodeMirror create(Element parent,
+      Configuration cfg) /*-{
     return $wnd.CodeMirror(parent, cfg);
   }-*/;
 
@@ -42,6 +43,30 @@
   public final native void refresh() /*-{ this.refresh(); }-*/;
   public final native Element getWrapperElement() /*-{ return this.getWrapperElement(); }-*/;
 
+  public final native void markText(LineCharacter from, LineCharacter to,
+      Configuration options) /*-{
+    this.markText(from, to, options);
+  }-*/;
+
+  public enum LineClassWhere {
+    TEXT, BACKGROUND, WRAP;
+  }
+
+  public final void addLineClass(int line, LineClassWhere where,
+      String className) {
+    addLineClassNative(line, where.name().toLowerCase(), className);
+  }
+
+  private final native void addLineClassNative(int line, String where,
+      String lineClass) /*-{
+    this.addLineClass(line, where, lineClass);
+  }-*/;
+
+  public final native void addLineWidget(int line, Element node,
+      Configuration options) /*-{
+    this.addLineWidget(line, node, options);
+  }-*/;
+
   protected CodeMirror() {
   }
 }
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
index a121697..57fcc2b 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Configuration.java
@@ -36,6 +36,9 @@
   public final native Configuration set(String name, boolean val)
   /*-{ this[name] = val; return this; }-*/;
 
+  public final native Configuration setInfinity(String name)
+  /*-{ this[name] = Infinity; return this; }-*/;
+
   protected Configuration() {
   }
 }
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
new file mode 100644
index 0000000..8a8e9d5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
@@ -0,0 +1,39 @@
+// 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.JavaScriptObject;
+
+/** {line, ch} objects used within CodeMirror. */
+public class LineCharacter extends JavaScriptObject {
+  public static LineCharacter create(int line, int ch) {
+    LineCharacter lineCh = createObject().cast();
+    return lineCh.setLine(line).setCh(ch);
+  }
+
+  private final native LineCharacter setLine(int line) /*-{
+    this.line = line; return this;
+  }-*/;
+
+  private final native LineCharacter setCh(int ch) /*-{
+    this.ch = ch; return this;
+  }-*/;
+
+  public final native int getLine() /*-{ return this.line; }-*/;
+  public final native int getCh() /*-{ return this.ch; }-*/;
+
+  protected LineCharacter() {
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
index 0637a1b..6373906d6 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
@@ -14,6 +14,7 @@
 
 package net.codemirror.lib;
 
+import com.google.gerrit.client.rpc.CallbackGroup;
 import com.google.gwt.core.client.Callback;
 import com.google.gwt.core.client.ScriptInjector;
 import com.google.gwt.dom.client.ScriptElement;
@@ -25,6 +26,8 @@
 import com.google.gwt.safehtml.shared.SafeUri;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
+import net.codemirror.addon.Addons;
+
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -36,8 +39,14 @@
     if (isLibLoaded()) {
       cb.onSuccess(null);
     } else {
+      CallbackGroup group = new CallbackGroup();
       injectCss(Lib.I.css());
-      injectScript(Lib.I.js().getSafeUri(), cb);
+      injectScript(
+          Lib.I.js().getSafeUri(), group.add(new AsyncCallback<Void>() {
+            public void onFailure(Throwable caught) {}
+            public void onSuccess(Void result) {}
+          }));
+      injectScript(Addons.I.mark_selection().getSafeUri(), group.add(cb));
     }
   }