diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java index 6bbb2dd227..3e5e29caa1 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java @@ -16,9 +16,6 @@ package com.google.gerrit.client.diff; import static com.google.gerrit.client.diff.DisplaySide.A; import static com.google.gerrit.client.diff.DisplaySide.B; -import static com.google.gerrit.client.diff.OverviewBar.MarkType.DELETE; -import static com.google.gerrit.client.diff.OverviewBar.MarkType.EDIT; -import static com.google.gerrit.client.diff.OverviewBar.MarkType.INSERT; import com.google.gerrit.client.diff.DiffInfo.Region; import com.google.gerrit.client.diff.DiffInfo.Span; @@ -79,7 +76,7 @@ class ChunkManager { private final SideBySide2 host; private final CodeMirror cmA; private final CodeMirror cmB; - private final OverviewBar sidePanel; + private final Scrollbar scrollbar; private final LineMapper mapper; private List chunks; @@ -91,11 +88,11 @@ class ChunkManager { ChunkManager(SideBySide2 host, CodeMirror cmA, CodeMirror cmB, - OverviewBar sidePanel) { + Scrollbar scrollbar) { this.host = host; this.cmA = cmA; this.cmB = cmB; - this.sidePanel = sidePanel; + this.scrollbar = scrollbar; this.mapper = new LineMapper(); } @@ -197,11 +194,11 @@ class ChunkManager { private void addGutterTag(Region region, int startA, int startB) { if (region.a() == null) { - sidePanel.add(cmB, startB, region.b().length(), INSERT); + scrollbar.insert(cmB, startB, region.b().length()); } else if (region.b() == null) { - sidePanel.add(cmA, startA, region.a().length(), DELETE); + scrollbar.delete(cmA, cmB, startA, region.a().length()); } else { - sidePanel.add(cmB, startB, region.b().length(), EDIT); + scrollbar.edit(cmB, startB, region.b().length()); } } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.css similarity index 100% rename from gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBoxUi.css rename to gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.css diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java index 1d7452085c..da96e9290d 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentBox.java @@ -19,6 +19,7 @@ import com.google.gwt.event.dom.client.MouseOutEvent; import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.event.dom.client.MouseOverEvent; import com.google.gwt.event.dom.client.MouseOverHandler; +import com.google.gwt.resources.client.CssResource; import com.google.gwt.user.client.ui.Composite; import net.codemirror.lib.CodeMirror; @@ -32,8 +33,22 @@ abstract class CommentBox extends Composite { Resources.I.style().ensureInjected(); } + interface Style extends CssResource { + String commentWidgets(); + String commentBox(); + String contents(); + String message(); + String header(); + String summary(); + String date(); + + String goPrev(); + String goNext(); + String goUp(); + } + private final CommentGroup group; - private OverviewBar.MarkHandle mark; + private ScrollbarAnnotation annotation; private FromTo fromTo; private TextMarker rangeMarker; private TextMarker rangeHighlightMarker; @@ -79,12 +94,12 @@ abstract class CommentBox extends Composite { return group.getCommentManager(); } - OverviewBar.MarkHandle getMark() { - return mark; + ScrollbarAnnotation getAnnotation() { + return annotation; } - void setMark(OverviewBar.MarkHandle mh) { - mark = mh; + void setAnnotation(ScrollbarAnnotation mh) { + annotation = mh; } void setRangeHighlight(boolean highlight) { diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java index 77c93a764a..b041306961 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentManager.java @@ -159,10 +159,9 @@ class CommentManager { getPatchSetIdFromSide(side), info); group.add(box); - box.setMark(host.diffTable.overview.add( + box.setAnnotation(host.diffTable.scrollbar.comment( host.getCmFromSide(side), - Math.max(0, info.line() - 1), 1, - OverviewBar.MarkType.COMMENT)); + Math.max(0, info.line() - 1))); published.put(info.id(), box); } } @@ -223,10 +222,9 @@ class CommentManager { } group.add(box); - box.setMark(host.diffTable.overview.add( + box.setAnnotation(host.diffTable.scrollbar.draft( host.getCmFromSide(side), - Math.max(0, info.line() - 1), 1, - OverviewBar.MarkType.DRAFT)); + Math.max(0, info.line() - 1))); return box; } 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 fa47954149..8bdc7477d1 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 @@ -61,7 +61,7 @@ class DiffTable extends Composite { @UiField Element cmA; @UiField Element cmB; - @UiField OverviewBar overview; + Scrollbar scrollbar; @UiField Element patchSetNavRow; @UiField Element patchSetNavCellA; @UiField Element patchSetNavCellB; @@ -92,6 +92,7 @@ class DiffTable extends Composite { PatchSetSelectBox2.link(patchSetSelectBoxA, patchSetSelectBoxB); initWidget(uiBinder.createAndBindUi(this)); + this.scrollbar = new Scrollbar(this); this.parent = parent; this.headerVisible = true; this.visibleA = true; @@ -211,7 +212,6 @@ class DiffTable extends Composite { } void refresh() { - overview.refresh(); if (header) { CodeMirror cm = parent.getCmFromSide(DisplaySide.A); diffHeaderText.getStyle().setMarginLeft( 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 f563776e11..1420d3b7e3 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 @@ -19,7 +19,8 @@ limitations under the License. xmlns:d='urn:import:com.google.gerrit.client.diff'> @external .CodeMirror, .CodeMirror-lines, .CodeMirror-selectedtext; - @external .CodeMirror-linenumber, .CodeMirror-vscrollbar .CodeMirror-scroll; + @external .CodeMirror-linenumber; + @external .CodeMirror-overlayscroll-vertical, .CodeMirror-scroll; @external .CodeMirror-dialog-bottom; @external .cm-animate-fat-cursor, .CodeMirror-cursor; @external .cm-searching, .cm-trailingspace, .cm-tab; @@ -75,14 +76,9 @@ limitations under the License. .hideA .psNavB, .hideA .b { width: 100% } .hideB .psNavA, .hideB .a { width: 100% } - .overview { - width: 10px; - vertical-align: top; - } - - /* Hide scrollbars, OverviewBar controls both views. */ - .difftable .CodeMirror-scroll { padding-right: 0; } - .difftable .CodeMirror-vscrollbar { display: none !important; } + /* Hide scrollbars on A, B controls both views. */ + .a .CodeMirror-scroll { margin-right: -36px; } + .a .CodeMirror-overlayscroll-vertical { display: none !important; } .showLineNumbers .b { border-left: none; } .b { border-left: 1px solid #ddd; } @@ -195,16 +191,13 @@ limitations under the License. -

-        
       
       
         
         
-        
       
     
     
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
index 2793d012cc..044c21a327 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DraftBox.java
@@ -236,7 +236,7 @@ class DraftBox extends CommentBox {
     getCommentManager().setUnsaved(this, false);
     setRangeHighlight(false);
     clearRange();
-    getMark().remove();
+    getAnnotation().remove();
     getCommentGroup().remove(this);
     getCm().focus();
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.java
deleted file mode 100644
index b9e6375080..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.java
+++ /dev/null
@@ -1,266 +0,0 @@
-//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 com.google.gerrit.client.diff;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.MouseDownEvent;
-import com.google.gwt.event.dom.client.MouseMoveEvent;
-import com.google.gwt.event.dom.client.MouseUpEvent;
-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.uibinder.client.UiHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTMLPanel;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.Widget;
-
-import net.codemirror.lib.CodeMirror;
-import net.codemirror.lib.LineCharacter;
-import net.codemirror.lib.ScrollInfo;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/** Displays overview of all edits and comments in this file. */
-class OverviewBar extends Composite implements ClickHandler {
-  interface Binder extends UiBinder {}
-  private static final Binder uiBinder = GWT.create(Binder.class);
-
-  interface Style extends CssResource {
-    String gutter();
-    String halfGutter();
-    String comment();
-    String draft();
-    String insert();
-    String delete();
-    String viewportDrag();
-  }
-
-  enum MarkType {
-    COMMENT, DRAFT, INSERT, DELETE, EDIT
-  }
-
-  @UiField Style style;
-  @UiField Label viewport;
-
-  private final List diff;
-  private final Set comments;
-  private CodeMirror cmB;
-
-  private boolean dragging;
-  private int startY;
-  private double ratio;
-
-  OverviewBar() {
-    initWidget(uiBinder.createAndBindUi(this));
-    diff = new ArrayList<>();
-    comments = new HashSet<>();
-    addDomHandler(this, ClickEvent.getType());
-  }
-
-  void init(CodeMirror cmB) {
-    this.cmB = cmB;
-  }
-
-  void refresh() {
-    update(cmB.getScrollInfo());
-  }
-
-  void update(ScrollInfo si) {
-    double viewHeight = si.getClientHeight();
-    double r = ratio(si);
-
-    com.google.gwt.dom.client.Style style = viewport.getElement().getStyle();
-    style.setTop(si.getTop() * r, Unit.PX);
-    style.setHeight(Math.max(10, viewHeight * r), Unit.PX);
-    getElement().getStyle().setHeight(viewHeight, Unit.PX);
-    for (MarkHandle info : diff) {
-      info.position(r);
-    }
-    for (MarkHandle info : comments) {
-      info.position(r);
-    }
-  }
-
-  @Override
-  protected void onUnload() {
-    super.onUnload();
-    if (dragging) {
-      DOM.releaseCapture(viewport.getElement());
-    }
-  }
-
-  @Override
-  public void onClick(ClickEvent e) {
-    if (e.getY() < viewport.getElement().getOffsetTop()) {
-      CodeMirror.handleVimKey(cmB, "");
-    } else {
-      CodeMirror.handleVimKey(cmB, "");
-    }
-    cmB.focus();
-  }
-
-  @UiHandler("viewport")
-  void onMouseDown(MouseDownEvent e) {
-    if (cmB != null) {
-      dragging = true;
-      ratio = ratio(cmB.getScrollInfo());
-      startY = e.getY();
-      viewport.addStyleName(style.viewportDrag());
-      DOM.setCapture(viewport.getElement());
-      e.preventDefault();
-      e.stopPropagation();
-    }
-  }
-
-  @UiHandler("viewport")
-  void onMouseMove(MouseMoveEvent e) {
-    if (dragging) {
-      int y = e.getRelativeY(getElement()) - startY;
-      cmB.scrollToY(Math.max(0, y / ratio));
-      e.preventDefault();
-      e.stopPropagation();
-    }
-  }
-
-  @UiHandler("viewport")
-  void onMouseUp(MouseUpEvent e) {
-    if (dragging) {
-      dragging = false;
-      DOM.releaseCapture(viewport.getElement());
-      viewport.removeStyleName(style.viewportDrag());
-      e.preventDefault();
-      e.stopPropagation();
-    }
-  }
-
-  private double ratio(ScrollInfo si) {
-    double barHeight = si.getClientHeight();
-    double contentHeight = si.getHeight();
-    return barHeight / contentHeight;
-  }
-
-  MarkHandle add(CodeMirror cm, int line, int height, MarkType type) {
-    MarkHandle mark = new MarkHandle(cm, line, height);
-    switch (type) {
-      case COMMENT:
-        mark.addStyleName(style.comment());
-        comments.add(mark);
-        break;
-      case DRAFT:
-        mark.addStyleName(style.draft());
-        mark.getElement().setInnerText("*");
-        comments.add(mark);
-        break;
-      case INSERT:
-        mark.addStyleName(style.insert());
-        diff.add(mark);
-        break;
-      case DELETE:
-        mark.addStyleName(style.delete());
-        diff.add(mark);
-        break;
-      case EDIT:
-        mark.edit = DOM.createDiv();
-        mark.edit.setClassName(style.halfGutter());
-        mark.getElement().appendChild(mark.edit);
-        mark.addStyleName(style.insert());
-        diff.add(mark);
-        break;
-    }
-    if (cmB != null) {
-      mark.position(ratio(cmB.getScrollInfo()));
-    }
-    ((HTMLPanel) getWidget()).add(mark);
-    return mark;
-  }
-
-  void clearDiffMarkers() {
-    for (MarkHandle mark : diff) {
-      mark.removeFromParent();
-    }
-    diff.clear();
-  }
-
-  class MarkHandle extends Widget implements ClickHandler {
-    private static final int MIN_HEIGHT = 3;
-
-    private final CodeMirror cm;
-    private final int line;
-    private final int height;
-    private Element edit;
-
-    MarkHandle(CodeMirror cm, int line, int height) {
-      this.cm = cm;
-      this.line = line;
-      this.height = height;
-
-      setElement((Element)(DOM.createDiv()));
-      setStyleName(style.gutter());
-      addDomHandler(this, ClickEvent.getType());
-    }
-
-    void position(double ratio) {
-      double y = cm.heightAtLine(line, "local");
-      getElement().getStyle().setTop(y * ratio, Unit.PX);
-      if (height > 1) {
-        double e = cm.heightAtLine(line + height, "local");
-        double h = Math.max(MIN_HEIGHT, (e - y) * ratio);
-        getElement().getStyle().setHeight(h, Unit.PX);
-        if (edit != null) {
-          edit.getStyle().setHeight(h, Unit.PX);
-        }
-      }
-    }
-
-    @Override
-    public void onClick(ClickEvent e) {
-      if (height == 1 || !visible()) {
-        e.stopPropagation();
-
-        double y = cm.heightAtLine(line, "local");
-        double viewport = cm.getScrollInfo().getClientHeight();
-        cm.setCursor(LineCharacter.create(line));
-        cm.scrollToY(y - 0.5 * viewport);
-        cm.focus();
-      }
-    }
-
-    private boolean visible() {
-      int markT = getElement().getOffsetTop();
-      int markE = markT + getElement().getOffsetHeight();
-
-      int viewT = viewport.getElement().getOffsetTop();
-      int viewE = viewT + viewport.getElement().getOffsetHeight();
-
-      return (viewT <= markT && markT < viewE) // mark top within viewport
-          || (viewT <= markE && markE < viewE) // mark end within viewport
-          || (markT <= viewT && viewE <= markE); // mark contains viewport
-    }
-
-    void remove() {
-      removeFromParent();
-      comments.remove(this);
-    }
-  }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.ui.xml
deleted file mode 100644
index a56c3da859..0000000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/OverviewBar.ui.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-  
-    .overview {
-      position: relative;
-    }
-    .gutter {
-      cursor: pointer;
-      position: absolute;
-      height: 3px;
-      width: 6px;
-      border: 1px solid grey;
-      margin-left: 1px;
-    }
-    .halfGutter {
-      cursor: pointer;
-      position: absolute;
-      height: 3px;
-      width: 3px;
-      background-color: #faa;
-    }
-    .comment, .draft {
-      background-color: #fcfa96;
-      z-index: 2;
-    }
-    .draft {
-      text-align: center;
-      font-size: small;
-      line-height: 0.5;
-      color: inherit !important;
-      text-decoration: none !important;
-    }
-    .delete {
-      background-color: #faa;
-    }
-    .insert {
-      background-color: #9f9;
-    }
-    .viewport {
-      position: absolute;
-      background-color: rgba(128, 128, 128, 0.50);
-      border: 1px solid #444;
-      width: 8px;
-      cursor: default;
-      z-index: 3;
-      border-radius: 4px;
-    }
-    .viewportDrag {
-      cursor: move;
-    }
-  
-  
-    
-  
-
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java
index 605935bb48..e379d60d65 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Resources.java
@@ -16,29 +16,16 @@ package com.google.gerrit.client.diff;
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.resources.client.ClientBundle;
-import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.resources.client.ImageResource;
 
 /** Resources used by diff. */
 interface Resources extends ClientBundle {
   static final Resources I = GWT.create(Resources.class);
 
-  @Source("CommentBoxUi.css") Style style();
+  @Source("CommentBox.css") CommentBox.Style style();
+  @Source("Scrollbar.css") Scrollbar.Style scrollbarStyle();
+
   @Source("goPrev.png") ImageResource goPrev();
   @Source("goNext.png") ImageResource goNext();
   @Source("goUp.png") ImageResource goUp();
-
-  interface Style extends CssResource {
-    String commentWidgets();
-    String commentBox();
-    String contents();
-    String message();
-    String header();
-    String summary();
-    String date();
-
-    String goPrev();
-    String goNext();
-    String goUp();
-  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java
index abff8ef95a..ea57e8a237 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollSynchronizer.java
@@ -22,7 +22,6 @@ import net.codemirror.lib.ScrollInfo;
 class ScrollSynchronizer {
   private DiffTable diffTable;
   private LineMapper mapper;
-  private OverviewBar overview;
   private ScrollCallback active;
   private ScrollCallback callbackA;
   private ScrollCallback callbackB;
@@ -32,7 +31,6 @@ class ScrollSynchronizer {
       LineMapper mapper) {
     this.diffTable = diffTable;
     this.mapper = mapper;
-    this.overview = diffTable.overview;
 
     callbackA = new ScrollCallback(cmA, cmB, DisplaySide.A);
     callbackB = new ScrollCallback(cmB, cmA, DisplaySide.B);
@@ -87,7 +85,6 @@ class ScrollSynchronizer {
       if (active == this) {
         ScrollInfo si = src.getScrollInfo();
         updateScreenHeader(si);
-        overview.update(si);
         dst.scrollTo(si.getLeft(), align(si.getTop()));
         state = 0;
       }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css
new file mode 100644
index 0000000000..a5351151f4
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.css
@@ -0,0 +1,58 @@
+/* Copyright (C) 2014 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.
+ */
+
+@external .CodeMirror;
+@external .CodeMirror-overlayscroll-horizontal;
+@external .CodeMirror-overlayscroll-vertical;
+
+.CodeMirror-overlayscroll-horizontal div {
+  min-width: 25px;
+}
+.CodeMirror-overlayscroll-vertical div {
+  min-height: 25px;
+}
+
+.CodeMirror .CodeMirror-overlayscroll-vertical {
+  z-index: inherit;
+}
+.CodeMirror .CodeMirror-overlayscroll-horizontal div,
+.CodeMirror .CodeMirror-overlayscroll-vertical div {
+  background-color: rgba(128, 128, 128, 0.50);
+  z-index: 8;
+}
+
+.comment, .draft, .insert, .delete, .edit {
+  min-height: 5px;
+  position: absolute;
+  right: 0;
+  z-index: 7;
+}
+
+.comment, .draft {
+  color: #0d0d0d;
+  font-size: 9px;
+}
+
+.delete {
+  background-color: #faa;
+}
+.insert {
+  background-color: #9f9;
+}
+.edit {
+  border-left: 3px solid #faa;
+  width: 2px !important;
+  background-color: #9f9;
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.java
new file mode 100644
index 0000000000..dce1c3fee6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Scrollbar.java
@@ -0,0 +1,98 @@
+// Copyright (C) 2014 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 com.google.gerrit.client.diff;
+
+import com.google.gwt.resources.client.CssResource;
+
+import net.codemirror.lib.CodeMirror;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Displays overview of all edits and comments in this file. */
+class Scrollbar {
+  static {
+    Resources.I.scrollbarStyle().ensureInjected();
+  }
+
+  interface Style extends CssResource {
+    String comment();
+    String draft();
+    String insert();
+    String delete();
+    String edit();
+  }
+
+  private final List diff = new ArrayList<>();
+  private final DiffTable parent;
+
+  Scrollbar(DiffTable d) {
+    parent = d;
+  }
+
+  ScrollbarAnnotation comment(CodeMirror cm, int line) {
+    ScrollbarAnnotation a = new ScrollbarAnnotation(cm);
+    a.setStyleName(Resources.I.scrollbarStyle().comment());
+    a.at(line);
+    a.getElement().setInnerText("\u2736"); // Six pointed black star
+    parent.add(a);
+    return a;
+  }
+
+  ScrollbarAnnotation draft(CodeMirror cm, int line) {
+    ScrollbarAnnotation a = new ScrollbarAnnotation(cm);
+    a.setStyleName(Resources.I.scrollbarStyle().draft());
+    a.at(line);
+    a.getElement().setInnerText("\u270D"); // Writing hand
+    parent.add(a);
+    return a;
+  }
+
+  ScrollbarAnnotation insert(CodeMirror cm, int line, int len) {
+    ScrollbarAnnotation a = diff(cm, line, len);
+    a.setStyleName(Resources.I.scrollbarStyle().insert());
+    parent.add(a);
+    return a;
+  }
+
+  ScrollbarAnnotation delete(CodeMirror cmA, CodeMirror cmB, int line, int len) {
+    ScrollbarAnnotation a = diff(cmA, line, len);
+    a.setStyleName(Resources.I.scrollbarStyle().delete());
+    a.renderOn(cmB);
+    parent.add(a);
+    return a;
+  }
+
+  ScrollbarAnnotation edit(CodeMirror cm, int line, int len) {
+    ScrollbarAnnotation a = diff(cm, line, len);
+    a.setStyleName(Resources.I.scrollbarStyle().edit());
+    parent.add(a);
+    return a;
+  }
+
+  private ScrollbarAnnotation diff(CodeMirror cm, int s, int n) {
+    ScrollbarAnnotation a = new ScrollbarAnnotation(cm);
+    a.at(CodeMirror.pos(s), CodeMirror.pos(s + n));
+    diff.add(a);
+    return a;
+  }
+
+  void removeDiffAnnotations() {
+    for (ScrollbarAnnotation a : diff) {
+      a.remove();
+    }
+    diff.clear();
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollbarAnnotation.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollbarAnnotation.java
new file mode 100644
index 0000000000..5d6654dfee
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ScrollbarAnnotation.java
@@ -0,0 +1,119 @@
+// Copyright (C) 2014 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 com.google.gerrit.client.diff;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.Widget;
+
+import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.CodeMirror.RegisteredHandler;
+import net.codemirror.lib.LineCharacter;
+
+/** Displayed on the vertical scrollbar to place a chunk or comment. */
+class ScrollbarAnnotation extends Widget implements ClickHandler {
+  private final CodeMirror cm;
+  private CodeMirror cmB;
+  private RegisteredHandler refresh;
+  private LineCharacter from;
+  private LineCharacter to;
+  private double scale;
+
+  ScrollbarAnnotation(CodeMirror cm) {
+    setElement((Element) DOM.createDiv());
+    getElement().setAttribute("not-content", "true");
+    addDomHandler(this, ClickEvent.getType());
+    this.cm = cm;
+    this.cmB = cm;
+  }
+
+  void remove() {
+    removeFromParent();
+  }
+
+  void at(int line) {
+    at(CodeMirror.pos(line), CodeMirror.pos(line + 1));
+  }
+
+  void at(LineCharacter from, LineCharacter to) {
+    this.from = from;
+    this.to = to;
+  }
+
+  void renderOn(CodeMirror cm) {
+    this.cmB = cm;
+  }
+
+  @Override
+  protected void onLoad() {
+    cmB.getWrapperElement().appendChild(getElement());
+    refresh = cmB.on("refresh", new Runnable() {
+      @Override
+      public void run() {
+        if (updateScale()) {
+          updatePosition();
+        }
+      }
+    });
+    updateScale();
+    updatePosition();
+  }
+
+  @Override
+  protected void onUnload() {
+    cmB.off("refresh", refresh);
+  }
+
+  private boolean updateScale() {
+    double old = scale;
+    double docHeight = cmB.getWrapperElement().getClientHeight();
+    double lineHeight = cmB.heightAtLine(cmB.lastLine() + 1, "local");
+    scale = (docHeight - cmB.barHeight()) / lineHeight;
+    return old != scale;
+  }
+
+  private void updatePosition() {
+    double top = cm.charCoords(from, "local").top() * scale;
+    double bottom = cm.charCoords(to, "local").bottom() * scale;
+
+    Element e = getElement();
+    e.getStyle().setTop(top, Unit.PX);
+    e.getStyle().setWidth(Math.max(2, cm.barWidth() - 1), Unit.PX);
+    e.getStyle().setHeight(Math.max(3, bottom - top), Unit.PX);
+  }
+
+  @Override
+  public void onClick(ClickEvent event) {
+    event.stopPropagation();
+
+    int line = from.getLine();
+    int h = to.getLine() - line;
+    if (h > 5) {
+      // Map click inside of the annotation to the relative position
+      // within the region covered by the annotation.
+      double s = ((double) event.getY()) / getElement().getOffsetHeight();
+      line += (int) (s * h);
+    }
+
+    double y = cm.heightAtLine(line, "local");
+    double viewport = cm.getScrollInfo().getClientHeight();
+    cm.setCursor(from);
+    cm.scrollTo(0, y - 0.5 * viewport);
+    cm.focus();
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
index 11c8745074..4944a35e8e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
@@ -553,8 +553,7 @@ public class SideBySide2 extends Screen {
 
     cmA = newCM(diff.meta_a(), diff.text_a(), DisplaySide.A, diffTable.cmA);
     cmB = newCM(diff.meta_b(), diff.text_b(), DisplaySide.B, diffTable.cmB);
-    diffTable.overview.init(cmB);
-    chunkManager = new ChunkManager(this, cmA, cmB, diffTable.overview);
+    chunkManager = new ChunkManager(this, cmA, cmB, diffTable.scrollbar);
     skipManager = new SkipManager(this, commentManager);
 
     columnMarginA = DOM.createDiv();
@@ -650,6 +649,7 @@ public class SideBySide2 extends Screen {
       .set("tabSize", prefs.tabSize())
       .set("mode", mode)
       .set("lineWrapping", false)
+      .set("scrollbarStyle", "overlay")
       .set("styleSelectedText", true)
       .set("showTrailingSpace", prefs.showWhitespaceErrors())
       .set("keyMap", "vim_ro")
@@ -790,7 +790,6 @@ public class SideBySide2 extends Screen {
       public void run() {
         skipManager.removeAll();
         skipManager.render(context, diff);
-        diffTable.overview.refresh();
       }
     });
   }
@@ -981,7 +980,6 @@ public class SideBySide2 extends Screen {
     int height = getCodeMirrorHeight();
     cmA.setHeight(height);
     cmB.setHeight(height);
-    diffTable.overview.refresh();
   }
 
   private int getCodeMirrorHeight() {
@@ -1084,7 +1082,7 @@ public class SideBySide2 extends Screen {
               public void run() {
                 skipManager.removeAll();
                 chunkManager.reset();
-                diffTable.overview.clearDiffMarkers();
+                diffTable.scrollbar.removeDiffAnnotations();
                 setShowIntraline(prefs.intralineDifference());
                 render(diff);
                 chunkManager.adjustPadding();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
index c69bc075be..1ed9c04e25 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
@@ -133,7 +133,6 @@ class SkipBar extends Composite {
   void expandBefore(int cnt) {
     expandSideBefore(cnt);
     otherBar.expandSideBefore(cnt);
-    manager.getOverviewBar().refresh();
   }
 
   private void expandSideBefore(int cnt) {
@@ -185,7 +184,6 @@ class SkipBar extends Composite {
     expandSideAll();
     otherBar.expandSideAll();
     manager.remove(this, otherBar);
-    manager.getOverviewBar().refresh();
   }
 
   @UiHandler("upArrow")
@@ -198,7 +196,6 @@ class SkipBar extends Composite {
   void onExpandAfter(@SuppressWarnings("unused") ClickEvent e) {
     expandAfter();
     otherBar.expandAfter();
-    manager.getOverviewBar().refresh();
     cm.focus();
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java
index 5ba275fd9e..0baada5b96 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipManager.java
@@ -38,10 +38,6 @@ class SkipManager {
     this.commentManager = commentManager;
   }
 
-  OverviewBar getOverviewBar() {
-    return host.diffTable.overview;
-  }
-
   void render(int context, DiffInfo diff) {
     if (context == AccountDiffPreference.WHOLE_FILE_CONTEXT) {
       return;
@@ -111,7 +107,6 @@ class SkipManager {
       for (SkipBar bar : skipBars) {
         bar.expandSideAll();
       }
-      getOverviewBar().refresh();
       skipBars = null;
       line0 = null;
     }
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 9cc00ca442..18970c9c77 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -69,6 +69,9 @@ public class CodeMirror extends JavaScriptObject {
   public final native void setHeight(double h) /*-{ this.setSize(null, h); }-*/;
   public final native void setHeight(String h) /*-{ this.setSize(null, h); }-*/;
   public final native String getLine(int n) /*-{ return this.getLine(n) }-*/;
+  public final native double barHeight() /*-{ return this.display.barHeight }-*/;
+  public final native double barWidth() /*-{ return this.display.barWidth }-*/;
+  public final native int lastLine() /*-{ return this.lastLine() }-*/;
 
   public final native void refresh() /*-{ this.refresh(); }-*/;
   public final native Element getWrapperElement() /*-{ return this.getWrapperElement(); }-*/;
@@ -178,10 +181,14 @@ public class CodeMirror extends JavaScriptObject {
     });
   }-*/;
 
-  public final native void on(String event, Runnable thunk) /*-{
-    this.on(event, $entry(function() {
-      thunk.@java.lang.Runnable::run()();
-    }));
+  public final native void off(String event, RegisteredHandler h) /*-{
+    this.off(event, h)
+  }-*/;
+
+  public final native RegisteredHandler on(String event, Runnable thunk) /*-{
+    var h = $entry(function() { thunk.@java.lang.Runnable::run()() });
+    this.on(event, h);
+    return h;
   }-*/;
 
   public final native void on(String event, EventHandler handler) /*-{
@@ -354,6 +361,11 @@ public class CodeMirror extends JavaScriptObject {
     }
   }
 
+  public static class RegisteredHandler extends JavaScriptObject {
+    protected RegisteredHandler() {
+    }
+  }
+
   public interface EventHandler {
     public void handle(CodeMirror instance, NativeEvent event);
   }
diff --git a/lib/codemirror/cm.defs b/lib/codemirror/cm.defs
index e8e9d4483f..eaba040375 100644
--- a/lib/codemirror/cm.defs
+++ b/lib/codemirror/cm.defs
@@ -1,6 +1,7 @@
 CM_CSS = [
   'lib/codemirror.css',
   'addon/dialog/dialog.css',
+  'addon/scroll/simplescrollbars.css',
 ]
 
 CM_THEMES = [
@@ -16,6 +17,7 @@ CM_JS = [
   'lib/codemirror.js',
   'keymap/vim.js',
   'addon/dialog/dialog.js',
+  'addon/scroll/simplescrollbars.js',
   'addon/search/searchcursor.js',
   'addon/search/search.js',
   'addon/selection/mark-selection.js',