Merge "New unified diff based on CodeMirror"
This commit is contained in:
@@ -83,6 +83,7 @@ import com.google.gerrit.client.dashboards.DashboardInfo;
|
||||
import com.google.gerrit.client.dashboards.DashboardList;
|
||||
import com.google.gerrit.client.diff.DisplaySide;
|
||||
import com.google.gerrit.client.diff.SideBySide;
|
||||
import com.google.gerrit.client.diff.Unified;
|
||||
import com.google.gerrit.client.documentation.DocScreen;
|
||||
import com.google.gerrit.client.editor.EditScreen;
|
||||
import com.google.gerrit.client.groups.GroupApi;
|
||||
@@ -471,14 +472,16 @@ public class Dispatcher {
|
||||
|
||||
if ("".equals(panel) || /* DEPRECATED URL */"cm".equals(panel)) {
|
||||
if (preferUnified()) {
|
||||
unified(token, baseId, id);
|
||||
unified(token, baseId, id, side, line);
|
||||
} else {
|
||||
codemirror(token, baseId, id, side, line, false);
|
||||
}
|
||||
} else if ("sidebyside".equals(panel)) {
|
||||
codemirror(token, null, id, side, line, false);
|
||||
} else if ("unified".equals(panel)) {
|
||||
unified(token, baseId, id);
|
||||
unified(token, baseId, id, side, line);
|
||||
} else if ("unified1".equals(panel)) {
|
||||
unified1(token, baseId, id);
|
||||
} else if ("edit".equals(panel)) {
|
||||
codemirror(token, null, id, side, line, true);
|
||||
} else {
|
||||
@@ -490,7 +493,18 @@ public class Dispatcher {
|
||||
return DiffView.UNIFIED_DIFF.equals(Gerrit.getUserPreferences().diffView());
|
||||
}
|
||||
|
||||
private static void unified(final String token,
|
||||
private static void unified(final String token, final PatchSet.Id baseId,
|
||||
final Patch.Key id, final DisplaySide side, final int line) {
|
||||
GWT.runAsync(new AsyncSplit(token) {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
Gerrit.display(token,
|
||||
new Unified(baseId, id.getParentKey(), id.get(), side, line));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void unified1(final String token,
|
||||
final PatchSet.Id baseId,
|
||||
final Patch.Key id) {
|
||||
GWT.runAsync(new AsyncSplit(token) {
|
||||
|
@@ -15,83 +15,31 @@
|
||||
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 com.google.gerrit.client.diff.DiffInfo.Region;
|
||||
import com.google.gerrit.client.diff.DiffInfo.Span;
|
||||
import com.google.gerrit.client.rpc.Natives;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
import com.google.gwt.core.client.JsArrayString;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.NativeEvent;
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.user.client.DOM;
|
||||
import com.google.gwt.user.client.EventListener;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.CodeMirror.LineClassWhere;
|
||||
import net.codemirror.lib.Configuration;
|
||||
import net.codemirror.lib.LineWidget;
|
||||
import net.codemirror.lib.Pos;
|
||||
import net.codemirror.lib.TextMarker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/** Colors modified regions for {@link SideBySide}. */
|
||||
class ChunkManager {
|
||||
private static final String DATA_LINES = "_cs2h";
|
||||
private static double guessedLineHeightPx = 15;
|
||||
private static final JavaScriptObject focusA = initOnClick(A);
|
||||
private static final JavaScriptObject focusB = initOnClick(B);
|
||||
private static final native JavaScriptObject initOnClick(DisplaySide s) /*-{
|
||||
return $entry(function(e){
|
||||
@com.google.gerrit.client.diff.ChunkManager::focus(
|
||||
Lcom/google/gwt/dom/client/NativeEvent;
|
||||
Lcom/google/gerrit/client/diff/DisplaySide;)(e,s)
|
||||
});
|
||||
}-*/;
|
||||
|
||||
private static void focus(NativeEvent event, DisplaySide side) {
|
||||
Element e = Element.as(event.getEventTarget());
|
||||
for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
|
||||
EventListener l = DOM.getEventListener(e);
|
||||
if (l instanceof SideBySide) {
|
||||
((SideBySide) l).getCmFromSide(side).focus();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void focusOnClick(Element e, DisplaySide side) {
|
||||
onClick(e, side == A ? focusA : focusB);
|
||||
}
|
||||
|
||||
private static final native void onClick(Element e, JavaScriptObject f)
|
||||
/** Colors modified regions for {@link SideBySide} and {@link Unified}. */
|
||||
abstract class ChunkManager {
|
||||
static final native void onClick(Element e, JavaScriptObject f)
|
||||
/*-{ e.onclick = f }-*/;
|
||||
|
||||
private final SideBySide host;
|
||||
private final CodeMirror cmA;
|
||||
private final CodeMirror cmB;
|
||||
private final Scrollbar scrollbar;
|
||||
private final LineMapper mapper;
|
||||
|
||||
private List<DiffChunkInfo> chunks;
|
||||
private List<TextMarker> markers;
|
||||
private List<Runnable> undo;
|
||||
private List<LineWidget> padding;
|
||||
private List<Element> paddingDivs;
|
||||
|
||||
ChunkManager(SideBySide host,
|
||||
CodeMirror cmA,
|
||||
CodeMirror cmB,
|
||||
Scrollbar scrollbar) {
|
||||
this.host = host;
|
||||
this.cmA = cmA;
|
||||
this.cmB = cmB;
|
||||
ChunkManager(Scrollbar scrollbar) {
|
||||
this.scrollbar = scrollbar;
|
||||
this.mapper = new LineMapper();
|
||||
}
|
||||
@@ -100,8 +48,14 @@ class ChunkManager {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
DiffChunkInfo getFirst() {
|
||||
return !chunks.isEmpty() ? chunks.get(0) : null;
|
||||
Scrollbar getScrollbar() {
|
||||
return scrollbar;
|
||||
}
|
||||
|
||||
abstract DiffChunkInfo getFirst();
|
||||
|
||||
List<TextMarker> getMarkers() {
|
||||
return markers;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
@@ -112,133 +66,20 @@ class ChunkManager {
|
||||
for (Runnable r : undo) {
|
||||
r.run();
|
||||
}
|
||||
for (LineWidget w : padding) {
|
||||
w.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void render(DiffInfo diff) {
|
||||
chunks = new ArrayList<>();
|
||||
abstract void render(DiffInfo diff);
|
||||
|
||||
void render() {
|
||||
markers = new ArrayList<>();
|
||||
undo = new ArrayList<>();
|
||||
padding = new ArrayList<>();
|
||||
paddingDivs = new ArrayList<>();
|
||||
|
||||
String diffColor = diff.metaA() == null || diff.metaB() == null
|
||||
? DiffTable.style.intralineBg()
|
||||
: DiffTable.style.diff();
|
||||
|
||||
for (Region current : Natives.asList(diff.content())) {
|
||||
if (current.ab() != null) {
|
||||
mapper.appendCommon(current.ab().length());
|
||||
} else if (current.skip() > 0) {
|
||||
mapper.appendCommon(current.skip());
|
||||
} else if (current.common()) {
|
||||
mapper.appendCommon(current.b().length());
|
||||
} else {
|
||||
render(current, diffColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (paddingDivs.isEmpty()) {
|
||||
paddingDivs = null;
|
||||
}
|
||||
}
|
||||
|
||||
void adjustPadding() {
|
||||
if (paddingDivs != null) {
|
||||
double h = cmB.extras().lineHeightPx();
|
||||
for (Element div : paddingDivs) {
|
||||
int lines = div.getPropertyInt(DATA_LINES);
|
||||
div.getStyle().setHeight(lines * h, Unit.PX);
|
||||
}
|
||||
for (LineWidget w : padding) {
|
||||
w.changed();
|
||||
}
|
||||
paddingDivs = null;
|
||||
guessedLineHeightPx = h;
|
||||
}
|
||||
}
|
||||
|
||||
private void render(Region region, String diffColor) {
|
||||
int startA = mapper.getLineA();
|
||||
int startB = mapper.getLineB();
|
||||
|
||||
JsArrayString a = region.a();
|
||||
JsArrayString b = region.b();
|
||||
int aLen = a != null ? a.length() : 0;
|
||||
int bLen = b != null ? b.length() : 0;
|
||||
|
||||
String color = a == null || b == null
|
||||
? diffColor
|
||||
: DiffTable.style.intralineBg();
|
||||
|
||||
colorLines(cmA, color, startA, aLen);
|
||||
colorLines(cmB, color, startB, bLen);
|
||||
markEdit(cmA, startA, a, region.editA());
|
||||
markEdit(cmB, startB, b, region.editB());
|
||||
addPadding(cmA, startA + aLen - 1, bLen - aLen);
|
||||
addPadding(cmB, startB + bLen - 1, aLen - bLen);
|
||||
addGutterTag(region, startA, startB);
|
||||
mapper.appendReplace(aLen, bLen);
|
||||
|
||||
int endA = mapper.getLineA() - 1;
|
||||
int endB = mapper.getLineB() - 1;
|
||||
if (aLen > 0) {
|
||||
addDiffChunk(cmB, endA, aLen, bLen > 0);
|
||||
}
|
||||
if (bLen > 0) {
|
||||
addDiffChunk(cmA, endB, bLen, aLen > 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void addGutterTag(Region region, int startA, int startB) {
|
||||
if (region.a() == null) {
|
||||
scrollbar.insert(cmB, startB, region.b().length());
|
||||
} else if (region.b() == null) {
|
||||
scrollbar.delete(cmA, cmB, startA, region.a().length());
|
||||
} else {
|
||||
scrollbar.edit(cmB, startB, region.b().length());
|
||||
}
|
||||
}
|
||||
|
||||
private void markEdit(CodeMirror cm, int startLine,
|
||||
JsArrayString lines, JsArray<Span> edits) {
|
||||
if (lines == null || edits == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
EditIterator iter = new EditIterator(lines, startLine);
|
||||
Configuration bg = Configuration.create()
|
||||
.set("className", DiffTable.style.intralineBg())
|
||||
.set("readOnly", true);
|
||||
|
||||
Configuration diff = Configuration.create()
|
||||
.set("className", DiffTable.style.diff())
|
||||
.set("readOnly", true);
|
||||
|
||||
Pos last = Pos.create(0, 0);
|
||||
for (Span span : Natives.asList(edits)) {
|
||||
Pos from = iter.advance(span.skip());
|
||||
Pos to = iter.advance(span.mark());
|
||||
if (from.line() == last.line()) {
|
||||
markers.add(cm.markText(last, from, bg));
|
||||
} else {
|
||||
markers.add(cm.markText(Pos.create(from.line(), 0), from, bg));
|
||||
}
|
||||
markers.add(cm.markText(from, to, diff));
|
||||
last = to;
|
||||
colorLines(cm, LineClassWhere.BACKGROUND,
|
||||
DiffTable.style.diff(),
|
||||
from.line(), to.line());
|
||||
}
|
||||
}
|
||||
|
||||
private void colorLines(CodeMirror cm, String color, int line, int cnt) {
|
||||
void colorLines(CodeMirror cm, String color, int line, int cnt) {
|
||||
colorLines(cm, LineClassWhere.WRAP, color, line, line + cnt);
|
||||
}
|
||||
|
||||
private void colorLines(final CodeMirror cm, final LineClassWhere where,
|
||||
void colorLines(final CodeMirror cm, final LineClassWhere where,
|
||||
final String className, final int start, final int end) {
|
||||
if (start < end) {
|
||||
for (int line = start; line < end; line++) {
|
||||
@@ -255,78 +96,35 @@ class ChunkManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new padding div below the given line.
|
||||
*
|
||||
* @param cm parent CodeMirror to add extra space into.
|
||||
* @param line line to put the padding below.
|
||||
* @param len number of lines to pad. Padding is inserted only if
|
||||
* {@code len >= 1}.
|
||||
*/
|
||||
private void addPadding(CodeMirror cm, int line, final int len) {
|
||||
if (0 < len) {
|
||||
Element pad = DOM.createDiv();
|
||||
pad.setClassName(DiffTable.style.padding());
|
||||
pad.setPropertyInt(DATA_LINES, len);
|
||||
pad.getStyle().setHeight(guessedLineHeightPx * len, Unit.PX);
|
||||
focusOnClick(pad, cm.side());
|
||||
paddingDivs.add(pad);
|
||||
padding.add(cm.addLineWidget(
|
||||
line == -1 ? 0 : line,
|
||||
pad,
|
||||
Configuration.create()
|
||||
.set("coverGutter", true)
|
||||
.set("noHScroll", true)
|
||||
.set("above", line == -1)));
|
||||
abstract Runnable diffChunkNav(final CodeMirror cm, final Direction dir);
|
||||
|
||||
void diffChunkNavHelper(List<? extends DiffChunkInfo> chunks, CodeMirror cm, int res, Direction dir) {
|
||||
if (res < 0) {
|
||||
res = -res - (dir == Direction.PREV ? 1 : 2);
|
||||
}
|
||||
res = res + (dir == Direction.PREV ? -1 : 1);
|
||||
if (res < 0 || chunks.size() <= res) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void addDiffChunk(CodeMirror cmToPad, int lineOnOther,
|
||||
int chunkSize, boolean edit) {
|
||||
chunks.add(new DiffChunkInfo(host.otherCm(cmToPad).side(),
|
||||
lineOnOther - chunkSize + 1, lineOnOther, edit));
|
||||
}
|
||||
|
||||
Runnable diffChunkNav(final CodeMirror cm, final Direction dir) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int line = cm.extras().hasActiveLine()
|
||||
? cm.getLineNumber(cm.extras().activeLine())
|
||||
: 0;
|
||||
int res = Collections.binarySearch(
|
||||
chunks,
|
||||
new DiffChunkInfo(cm.side(), line, 0, false),
|
||||
getDiffChunkComparator());
|
||||
if (res < 0) {
|
||||
res = -res - (dir == Direction.PREV ? 1 : 2);
|
||||
}
|
||||
res = res + (dir == Direction.PREV ? -1 : 1);
|
||||
if (res < 0 || chunks.size() <= res) {
|
||||
return;
|
||||
}
|
||||
|
||||
DiffChunkInfo lookUp = chunks.get(res);
|
||||
// If edit, skip the deletion chunk and set focus on the insertion one.
|
||||
if (lookUp.isEdit() && lookUp.getSide() == A) {
|
||||
res = res + (dir == Direction.PREV ? -1 : 1);
|
||||
if (res < 0 || chunks.size() <= res) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DiffChunkInfo target = chunks.get(res);
|
||||
CodeMirror targetCm = host.getCmFromSide(target.getSide());
|
||||
targetCm.setCursor(Pos.create(target.getStart(), 0));
|
||||
targetCm.focus();
|
||||
targetCm.scrollToY(
|
||||
targetCm.heightAtLine(target.getStart(), "local") -
|
||||
0.5 * cmB.scrollbarV().getClientHeight());
|
||||
DiffChunkInfo lookUp = chunks.get(res);
|
||||
// If edit, skip the deletion chunk and set focus on the insertion one.
|
||||
if (lookUp.isEdit() && lookUp.getSide() == A) {
|
||||
res = res + (dir == Direction.PREV ? -1 : 1);
|
||||
if (res < 0 || chunks.size() <= res) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DiffChunkInfo target = chunks.get(res);
|
||||
cm.setCursor(Pos.create(getCmLine(target.getStart(), target.getSide())));
|
||||
cm.focus();
|
||||
cm.scrollToY(
|
||||
cm.heightAtLine(target.getStart(), "local") -
|
||||
0.5 * cm.scrollbarV().getClientHeight());
|
||||
}
|
||||
|
||||
private Comparator<DiffChunkInfo> getDiffChunkComparator() {
|
||||
Comparator<DiffChunkInfo> getDiffChunkComparator() {
|
||||
// Chunks are ordered by their starting line. If it's a deletion,
|
||||
// use its corresponding line on the revision side for comparison.
|
||||
// In the edit case, put the deletion chunk right before the
|
||||
@@ -349,23 +147,5 @@ class ChunkManager {
|
||||
};
|
||||
}
|
||||
|
||||
DiffChunkInfo getDiffChunk(DisplaySide side, int line) {
|
||||
int res = Collections.binarySearch(
|
||||
chunks,
|
||||
new DiffChunkInfo(side, line, 0, false), // Dummy DiffChunkInfo
|
||||
getDiffChunkComparator());
|
||||
if (res >= 0) {
|
||||
return chunks.get(res);
|
||||
} else { // The line might be within a DiffChunk
|
||||
res = -res - 1;
|
||||
if (res > 0) {
|
||||
DiffChunkInfo info = chunks.get(res - 1);
|
||||
if (info.getSide() == side && info.getStart() <= line &&
|
||||
line <= info.getEnd()) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
abstract int getCmLine(int line, DisplaySide side);
|
||||
}
|
@@ -61,7 +61,7 @@ abstract class CommentBox extends Composite {
|
||||
fromTo.from(),
|
||||
fromTo.to(),
|
||||
Configuration.create()
|
||||
.set("className", DiffTable.style.range()));
|
||||
.set("className", Resources.I.diffTableStyle().range()));
|
||||
}
|
||||
addDomHandler(new MouseOverHandler() {
|
||||
@Override
|
||||
@@ -109,7 +109,7 @@ abstract class CommentBox extends Composite {
|
||||
fromTo.from(),
|
||||
fromTo.to(),
|
||||
Configuration.create()
|
||||
.set("className", DiffTable.style.rangeHighlight()));
|
||||
.set("className", Resources.I.diffTableStyle().rangeHighlight()));
|
||||
} else if (!highlight && rangeHighlightMarker != null) {
|
||||
rangeHighlightMarker.clear();
|
||||
rangeHighlightMarker = null;
|
||||
|
@@ -14,9 +14,6 @@
|
||||
|
||||
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.user.client.DOM;
|
||||
import com.google.gwt.user.client.Timer;
|
||||
import com.google.gwt.user.client.ui.Composite;
|
||||
import com.google.gwt.user.client.ui.FlowPanel;
|
||||
@@ -30,39 +27,28 @@ import net.codemirror.lib.TextMarker.FromTo;
|
||||
/**
|
||||
* LineWidget attached to a CodeMirror container.
|
||||
*
|
||||
* When a comment is placed on a line a CommentWidget is created on both sides.
|
||||
* The group tracks all comment boxes on that same line, and also includes an
|
||||
* empty padding element to keep subsequent lines vertically aligned.
|
||||
* When a comment is placed on a line a CommentWidget is created.
|
||||
*/
|
||||
class CommentGroup extends Composite {
|
||||
static void pair(CommentGroup a, CommentGroup b) {
|
||||
a.peer = b;
|
||||
b.peer = a;
|
||||
}
|
||||
abstract class CommentGroup extends Composite {
|
||||
|
||||
private final CommentManager manager;
|
||||
private final CodeMirror cm;
|
||||
private final DisplaySide side;
|
||||
private final int line;
|
||||
private final FlowPanel comments;
|
||||
private final Element padding;
|
||||
private LineWidget lineWidget;
|
||||
private Timer resizeTimer;
|
||||
private CommentGroup peer;
|
||||
|
||||
CommentGroup(CommentManager manager, CodeMirror cm, int line) {
|
||||
CommentGroup(CommentManager manager, CodeMirror cm, DisplaySide side, int line) {
|
||||
this.manager = manager;
|
||||
this.cm = cm;
|
||||
this.side = side;
|
||||
this.line = line;
|
||||
|
||||
comments = new FlowPanel();
|
||||
comments.setStyleName(Resources.I.style().commentWidgets());
|
||||
comments.setVisible(false);
|
||||
initWidget(new SimplePanel(comments));
|
||||
|
||||
padding = DOM.createDiv();
|
||||
padding.setClassName(DiffTable.style.padding());
|
||||
ChunkManager.focusOnClick(padding, cm.side());
|
||||
getElement().appendChild(padding);
|
||||
}
|
||||
|
||||
CommentManager getCommentManager() {
|
||||
@@ -73,10 +59,6 @@ class CommentGroup extends Composite {
|
||||
return cm;
|
||||
}
|
||||
|
||||
CommentGroup getPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
int getLine() {
|
||||
return line;
|
||||
}
|
||||
@@ -138,33 +120,19 @@ class CommentGroup extends Composite {
|
||||
void remove(DraftBox box) {
|
||||
comments.remove(box);
|
||||
comments.setVisible(0 < getBoxCount());
|
||||
|
||||
if (0 < getBoxCount() || 0 < peer.getBoxCount()) {
|
||||
resize();
|
||||
} else {
|
||||
detach();
|
||||
peer.detach();
|
||||
}
|
||||
}
|
||||
|
||||
private void detach() {
|
||||
void detach() {
|
||||
if (lineWidget != null) {
|
||||
lineWidget.clear();
|
||||
lineWidget = null;
|
||||
updateSelection();
|
||||
}
|
||||
manager.clearLine(cm.side(), line, this);
|
||||
manager.clearLine(side, line, this);
|
||||
removeFromParent();
|
||||
}
|
||||
|
||||
void attachPair(DiffTable parent) {
|
||||
if (lineWidget == null && peer.lineWidget == null) {
|
||||
this.attach(parent);
|
||||
peer.attach(parent);
|
||||
}
|
||||
}
|
||||
|
||||
private void attach(DiffTable parent) {
|
||||
void attach(DiffTable parent) {
|
||||
parent.add(this);
|
||||
lineWidget = cm.addLineWidget(Math.max(0, line - 1), getElement(),
|
||||
Configuration.create()
|
||||
@@ -174,33 +142,6 @@ class CommentGroup extends Composite {
|
||||
.set("insertAt", 0));
|
||||
}
|
||||
|
||||
void handleRedraw() {
|
||||
lineWidget.onRedraw(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (canComputeHeight() && peer.canComputeHeight()) {
|
||||
if (resizeTimer != null) {
|
||||
resizeTimer.cancel();
|
||||
resizeTimer = null;
|
||||
}
|
||||
adjustPadding(CommentGroup.this, peer);
|
||||
} else if (resizeTimer == null) {
|
||||
resizeTimer = new Timer() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (canComputeHeight() && peer.canComputeHeight()) {
|
||||
cancel();
|
||||
resizeTimer = null;
|
||||
adjustPadding(CommentGroup.this, peer);
|
||||
}
|
||||
}
|
||||
};
|
||||
resizeTimer.scheduleRepeating(5);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUnload() {
|
||||
super.onUnload();
|
||||
@@ -209,13 +150,7 @@ class CommentGroup extends Composite {
|
||||
}
|
||||
}
|
||||
|
||||
void resize() {
|
||||
if (lineWidget != null) {
|
||||
adjustPadding(this, peer);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSelection() {
|
||||
void updateSelection() {
|
||||
if (cm.somethingSelected()) {
|
||||
FromTo r = cm.getSelectedRange();
|
||||
if (r.to().line() >= line) {
|
||||
@@ -224,27 +159,37 @@ class CommentGroup extends Composite {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canComputeHeight() {
|
||||
boolean canComputeHeight() {
|
||||
return !comments.isVisible() || comments.getOffsetHeight() > 0;
|
||||
}
|
||||
|
||||
private int computeHeight() {
|
||||
if (comments.isVisible()) {
|
||||
// Include margin-bottom: 5px from CSS class.
|
||||
return comments.getOffsetHeight() + 5;
|
||||
}
|
||||
return 0;
|
||||
LineWidget getLineWidget() {
|
||||
return lineWidget;
|
||||
}
|
||||
|
||||
private static void adjustPadding(CommentGroup a, CommentGroup b) {
|
||||
int apx = a.computeHeight();
|
||||
int bpx = b.computeHeight();
|
||||
int h = Math.max(apx, bpx);
|
||||
a.padding.getStyle().setHeight(Math.max(0, h - apx), Unit.PX);
|
||||
b.padding.getStyle().setHeight(Math.max(0, h - bpx), Unit.PX);
|
||||
a.lineWidget.changed();
|
||||
b.lineWidget.changed();
|
||||
a.updateSelection();
|
||||
b.updateSelection();
|
||||
void setLineWidget(LineWidget widget) {
|
||||
lineWidget = widget;
|
||||
}
|
||||
|
||||
Timer getResizeTimer() {
|
||||
return resizeTimer;
|
||||
}
|
||||
|
||||
void setResizeTimer(Timer timer) {
|
||||
resizeTimer = timer;
|
||||
}
|
||||
|
||||
FlowPanel getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
CommentManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
abstract void init(DiffTable parent);
|
||||
|
||||
abstract void handleRedraw();
|
||||
|
||||
abstract void resize();
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2013 The Android Open Source Project
|
||||
// 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.
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package com.google.gerrit.client.diff;
|
||||
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.changes.CommentInfo;
|
||||
import com.google.gerrit.client.patches.SkippedLine;
|
||||
import com.google.gerrit.client.rpc.CallbackGroup;
|
||||
@@ -25,41 +24,32 @@ import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.CodeMirror.LineHandle;
|
||||
import net.codemirror.lib.Pos;
|
||||
import net.codemirror.lib.TextMarker.FromTo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/** Tracks comment widgets for {@link SideBySide}. */
|
||||
class CommentManager {
|
||||
private final SideBySide host;
|
||||
/** Tracks comment widgets for {@link DiffScreen}. */
|
||||
abstract class CommentManager {
|
||||
private final PatchSet.Id base;
|
||||
private final PatchSet.Id revision;
|
||||
private final String path;
|
||||
private final CommentLinkProcessor commentLinkProcessor;
|
||||
|
||||
private final Map<String, PublishedBox> published;
|
||||
private final SortedMap<Integer, CommentGroup> sideA;
|
||||
private final SortedMap<Integer, CommentGroup> sideB;
|
||||
private final Set<DraftBox> unsavedDrafts;
|
||||
private boolean attached;
|
||||
private boolean expandAll;
|
||||
private boolean open;
|
||||
|
||||
CommentManager(SideBySide host,
|
||||
PatchSet.Id base, PatchSet.Id revision,
|
||||
CommentManager(
|
||||
PatchSet.Id base,
|
||||
PatchSet.Id revision,
|
||||
String path,
|
||||
CommentLinkProcessor clp,
|
||||
boolean open) {
|
||||
this.host = host;
|
||||
this.base = base;
|
||||
this.revision = revision;
|
||||
this.path = path;
|
||||
@@ -67,111 +57,42 @@ class CommentManager {
|
||||
this.open = open;
|
||||
|
||||
published = new HashMap<>();
|
||||
sideA = new TreeMap<>();
|
||||
sideB = new TreeMap<>();
|
||||
unsavedDrafts = new HashSet<>();
|
||||
}
|
||||
|
||||
SideBySide getSideBySide() {
|
||||
return host;
|
||||
void setAttached(boolean attached) {
|
||||
this.attached = attached;
|
||||
}
|
||||
|
||||
void setExpandAllComments(boolean b) {
|
||||
expandAll = b;
|
||||
for (CommentGroup g : sideA.values()) {
|
||||
g.setOpenAll(b);
|
||||
}
|
||||
for (CommentGroup g : sideB.values()) {
|
||||
g.setOpenAll(b);
|
||||
}
|
||||
boolean isAttached() {
|
||||
return attached;
|
||||
}
|
||||
|
||||
Runnable commentNav(final CodeMirror src, final Direction dir) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Every comment appears in both side maps as a linked pair.
|
||||
// It is only necessary to search one side to find a comment
|
||||
// on either side of the editor pair.
|
||||
SortedMap<Integer, CommentGroup> map = map(src.side());
|
||||
int line = src.extras().hasActiveLine()
|
||||
? src.getLineNumber(src.extras().activeLine()) + 1
|
||||
: 0;
|
||||
if (dir == Direction.NEXT) {
|
||||
map = map.tailMap(line + 1);
|
||||
if (map.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
line = map.firstKey();
|
||||
} else {
|
||||
map = map.headMap(line);
|
||||
if (map.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
line = map.lastKey();
|
||||
}
|
||||
|
||||
CommentGroup g = map.get(line);
|
||||
if (g.getBoxCount() == 0) {
|
||||
g = g.getPeer();
|
||||
}
|
||||
|
||||
CodeMirror cm = g.getCm();
|
||||
double y = cm.heightAtLine(g.getLine() - 1, "local");
|
||||
cm.setCursor(Pos.create(g.getLine() - 1));
|
||||
cm.scrollToY(y - 0.5 * cm.scrollbarV().getClientHeight());
|
||||
cm.focus();
|
||||
}
|
||||
};
|
||||
void setExpandAll(boolean expandAll) {
|
||||
this.expandAll = expandAll;
|
||||
}
|
||||
|
||||
void render(CommentsCollections in, boolean expandAll) {
|
||||
if (in.publishedBase != null) {
|
||||
renderPublished(DisplaySide.A, in.publishedBase);
|
||||
}
|
||||
if (in.publishedRevision != null) {
|
||||
renderPublished(DisplaySide.B, in.publishedRevision);
|
||||
}
|
||||
if (in.draftsBase != null) {
|
||||
renderDrafts(DisplaySide.A, in.draftsBase);
|
||||
}
|
||||
if (in.draftsRevision != null) {
|
||||
renderDrafts(DisplaySide.B, in.draftsRevision);
|
||||
}
|
||||
if (expandAll) {
|
||||
setExpandAllComments(true);
|
||||
}
|
||||
for (CommentGroup g : sideA.values()) {
|
||||
g.attachPair(host.diffTable);
|
||||
}
|
||||
for (CommentGroup g : sideB.values()) {
|
||||
g.attachPair(host.diffTable);
|
||||
g.handleRedraw();
|
||||
}
|
||||
attached = true;
|
||||
boolean isExpandAll() {
|
||||
return expandAll;
|
||||
}
|
||||
|
||||
private void renderPublished(DisplaySide forSide, JsArray<CommentInfo> in) {
|
||||
for (CommentInfo info : Natives.asList(in)) {
|
||||
DisplaySide side = displaySide(info, forSide);
|
||||
if (side != null) {
|
||||
CommentGroup group = group(side, info.line());
|
||||
PublishedBox box = new PublishedBox(
|
||||
group,
|
||||
commentLinkProcessor,
|
||||
getPatchSetIdFromSide(side),
|
||||
info,
|
||||
open);
|
||||
group.add(box);
|
||||
box.setAnnotation(host.diffTable.scrollbar.comment(
|
||||
host.getCmFromSide(side),
|
||||
Math.max(0, info.line() - 1)));
|
||||
published.put(info.id(), box);
|
||||
}
|
||||
}
|
||||
boolean isOpen() {
|
||||
return open;
|
||||
}
|
||||
|
||||
private void renderDrafts(DisplaySide forSide, JsArray<CommentInfo> in) {
|
||||
String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
Map<String, PublishedBox> getPublished() {
|
||||
return published;
|
||||
}
|
||||
|
||||
CommentLinkProcessor getCommentLinkProcessor() {
|
||||
return commentLinkProcessor;
|
||||
}
|
||||
|
||||
void renderDrafts(DisplaySide forSide, JsArray<CommentInfo> in) {
|
||||
for (CommentInfo info : Natives.asList(in)) {
|
||||
DisplaySide side = displaySide(info, forSide);
|
||||
if (side != null) {
|
||||
@@ -180,197 +101,6 @@ class CommentManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DraftBox} at the specified line and focus it.
|
||||
*
|
||||
* @param side which side the draft will appear on.
|
||||
* @param line the line the draft will be at. Lines are 1-based. Line 0 is a
|
||||
* special case creating a file level comment.
|
||||
*/
|
||||
void insertNewDraft(DisplaySide side, int line) {
|
||||
if (line == 0) {
|
||||
host.getSkipManager().ensureFirstLineIsVisible();
|
||||
}
|
||||
|
||||
CommentGroup group = group(side, line);
|
||||
if (0 < group.getBoxCount()) {
|
||||
CommentBox last = group.getCommentBox(group.getBoxCount() - 1);
|
||||
if (last instanceof DraftBox) {
|
||||
((DraftBox)last).setEdit(true);
|
||||
} else {
|
||||
((PublishedBox)last).doReply();
|
||||
}
|
||||
} else {
|
||||
addDraftBox(side, CommentInfo.create(
|
||||
path,
|
||||
getStoredSideFromDisplaySide(side),
|
||||
line,
|
||||
null)).setEdit(true);
|
||||
}
|
||||
}
|
||||
|
||||
DraftBox addDraftBox(DisplaySide side, CommentInfo info) {
|
||||
CommentGroup group = group(side, info.line());
|
||||
DraftBox box = new DraftBox(
|
||||
group,
|
||||
commentLinkProcessor,
|
||||
getPatchSetIdFromSide(side),
|
||||
info,
|
||||
expandAll);
|
||||
|
||||
if (info.inReplyTo() != null) {
|
||||
PublishedBox r = published.get(info.inReplyTo());
|
||||
if (r != null) {
|
||||
r.setReplyBox(box);
|
||||
}
|
||||
}
|
||||
|
||||
group.add(box);
|
||||
box.setAnnotation(host.diffTable.scrollbar.draft(
|
||||
host.getCmFromSide(side),
|
||||
Math.max(0, info.line() - 1)));
|
||||
return box;
|
||||
}
|
||||
|
||||
private DisplaySide displaySide(CommentInfo info, DisplaySide forSide) {
|
||||
if (info.side() == Side.PARENT) {
|
||||
return base == null ? DisplaySide.A : null;
|
||||
}
|
||||
return forSide;
|
||||
}
|
||||
|
||||
List<SkippedLine> splitSkips(int context, List<SkippedLine> skips) {
|
||||
if (sideB.containsKey(0)) {
|
||||
// Special case of file comment; cannot skip first line.
|
||||
for (SkippedLine skip : skips) {
|
||||
if (skip.getStartB() == 0) {
|
||||
skip.incrementStart(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is not optimal, but shouldn't be too costly in most cases.
|
||||
// Maybe rewrite after done keeping track of diff chunk positions.
|
||||
for (int boxLine : sideB.tailMap(1).keySet()) {
|
||||
List<SkippedLine> temp = new ArrayList<>(skips.size() + 2);
|
||||
for (SkippedLine skip : skips) {
|
||||
int startLine = skip.getStartB();
|
||||
int deltaBefore = boxLine - startLine;
|
||||
int deltaAfter = startLine + skip.getSize() - boxLine;
|
||||
if (deltaBefore < -context || deltaAfter < -context) {
|
||||
temp.add(skip); // Size guaranteed to be greater than 1
|
||||
} else if (deltaBefore > context && deltaAfter > context) {
|
||||
SkippedLine before = new SkippedLine(
|
||||
skip.getStartA(), skip.getStartB(),
|
||||
skip.getSize() - deltaAfter - context);
|
||||
skip.incrementStart(deltaBefore + context);
|
||||
checkAndAddSkip(temp, before);
|
||||
checkAndAddSkip(temp, skip);
|
||||
} else if (deltaAfter > context) {
|
||||
skip.incrementStart(deltaBefore + context);
|
||||
checkAndAddSkip(temp, skip);
|
||||
} else if (deltaBefore > context) {
|
||||
skip.reduceSize(deltaAfter + context);
|
||||
checkAndAddSkip(temp, skip);
|
||||
}
|
||||
}
|
||||
if (temp.isEmpty()) {
|
||||
return temp;
|
||||
}
|
||||
skips = temp;
|
||||
}
|
||||
return skips;
|
||||
}
|
||||
|
||||
private static void checkAndAddSkip(List<SkippedLine> out, SkippedLine s) {
|
||||
if (s.getSize() > 1) {
|
||||
out.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
void clearLine(DisplaySide side, int line, CommentGroup group) {
|
||||
SortedMap<Integer, CommentGroup> map = map(side);
|
||||
if (map.get(line) == group) {
|
||||
map.remove(line);
|
||||
}
|
||||
}
|
||||
|
||||
Runnable toggleOpenBox(final CodeMirror cm) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
CommentGroup w = map(cm.side()).get(
|
||||
cm.getLineNumber(cm.extras().activeLine()) + 1);
|
||||
if (w != null) {
|
||||
w.openCloseLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Runnable openCloseAll(final CodeMirror cm) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
CommentGroup w = map(cm.side()).get(
|
||||
cm.getLineNumber(cm.extras().activeLine()) + 1);
|
||||
if (w != null) {
|
||||
w.openCloseAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Runnable insertNewDraft(final CodeMirror cm) {
|
||||
if (!Gerrit.isSignedIn()) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String token = host.getToken();
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
LineHandle handle = cm.extras().activeLine();
|
||||
int line = cm.getLineNumber(handle) + 1;
|
||||
token += "@" + (cm.side() == DisplaySide.A ? "a" : "") + line;
|
||||
}
|
||||
Gerrit.doSignIn(token);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
newDraft(cm, cm.getLineNumber(cm.extras().activeLine()) + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void newDraft(CodeMirror cm, int line) {
|
||||
if (cm.somethingSelected()) {
|
||||
FromTo fromTo = cm.getSelectedRange();
|
||||
Pos end = fromTo.to();
|
||||
if (end.ch() == 0) {
|
||||
end.line(end.line() - 1);
|
||||
end.ch(cm.getLine(end.line()).length());
|
||||
}
|
||||
|
||||
addDraftBox(cm.side(), CommentInfo.create(
|
||||
path,
|
||||
getStoredSideFromDisplaySide(cm.side()),
|
||||
line,
|
||||
CommentRange.create(fromTo))).setEdit(true);
|
||||
cm.setSelection(cm.getCursor());
|
||||
} else {
|
||||
insertNewDraft(cm.side(), line);
|
||||
}
|
||||
}
|
||||
|
||||
void setUnsaved(DraftBox box, boolean isUnsaved) {
|
||||
if (isUnsaved) {
|
||||
unsavedDrafts.add(box);
|
||||
@@ -385,52 +115,42 @@ class CommentManager {
|
||||
}
|
||||
}
|
||||
|
||||
private CommentGroup group(DisplaySide side, int line) {
|
||||
CommentGroup w = map(side).get(line);
|
||||
if (w != null) {
|
||||
return w;
|
||||
}
|
||||
|
||||
int lineA;
|
||||
int lineB;
|
||||
if (line == 0) {
|
||||
lineA = lineB = 0;
|
||||
} else if (side == DisplaySide.A) {
|
||||
lineA = line;
|
||||
lineB = host.lineOnOther(side, line - 1).getLine() + 1;
|
||||
} else {
|
||||
lineA = host.lineOnOther(side, line - 1).getLine() + 1;
|
||||
lineB = line;
|
||||
}
|
||||
|
||||
CommentGroup a = newGroup(DisplaySide.A, lineA);
|
||||
CommentGroup b = newGroup(DisplaySide.B, lineB);
|
||||
CommentGroup.pair(a, b);
|
||||
|
||||
sideA.put(lineA, a);
|
||||
sideB.put(lineB, b);
|
||||
|
||||
if (attached) {
|
||||
a.attachPair(host.diffTable);
|
||||
b.handleRedraw();
|
||||
}
|
||||
|
||||
return side == DisplaySide.A ? a : b;
|
||||
}
|
||||
|
||||
private CommentGroup newGroup(DisplaySide side, int line) {
|
||||
return new CommentGroup(this, host.getCmFromSide(side), line);
|
||||
}
|
||||
|
||||
private SortedMap<Integer, CommentGroup> map(DisplaySide side) {
|
||||
return side == DisplaySide.A ? sideA : sideB;
|
||||
}
|
||||
|
||||
private Side getStoredSideFromDisplaySide(DisplaySide side) {
|
||||
Side getStoredSideFromDisplaySide(DisplaySide side) {
|
||||
return side == DisplaySide.A && base == null ? Side.PARENT : Side.REVISION;
|
||||
}
|
||||
|
||||
private PatchSet.Id getPatchSetIdFromSide(DisplaySide side) {
|
||||
PatchSet.Id getPatchSetIdFromSide(DisplaySide side) {
|
||||
return side == DisplaySide.A && base != null ? base : revision;
|
||||
}
|
||||
|
||||
DisplaySide displaySide(CommentInfo info, DisplaySide forSide) {
|
||||
if (info.side() == Side.PARENT) {
|
||||
return base == null ? DisplaySide.A : null;
|
||||
}
|
||||
return forSide;
|
||||
}
|
||||
|
||||
abstract void insertNewDraft(DisplaySide side, int line);
|
||||
|
||||
abstract Runnable newDraftCallback(final CodeMirror cm);
|
||||
|
||||
abstract DraftBox addDraftBox(DisplaySide side, CommentInfo info);
|
||||
|
||||
abstract void setExpandAllComments(boolean b);
|
||||
|
||||
abstract Runnable commentNav(CodeMirror src, Direction dir);
|
||||
|
||||
abstract void clearLine(DisplaySide side, int line, CommentGroup group);
|
||||
|
||||
abstract void renderPublished(DisplaySide forSide, JsArray<CommentInfo> in);
|
||||
|
||||
abstract List<SkippedLine> splitSkips(int context, List<SkippedLine> skips);
|
||||
|
||||
abstract void newDraftOnGutterClick(CodeMirror cm, String gutterClass, int line);
|
||||
|
||||
abstract Runnable toggleOpenBox(final CodeMirror cm);
|
||||
|
||||
abstract Runnable openCloseAll(final CodeMirror cm);
|
||||
|
||||
abstract DiffScreen getDiffScreen();
|
||||
}
|
||||
|
@@ -118,6 +118,26 @@ public class DiffInfo extends JavaScriptObject {
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
public final String textUnified() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
JsArray<Region> c = content();
|
||||
for (int i = 0; i < c.length(); i++) {
|
||||
Region r = c.get(i);
|
||||
if (r.ab() != null) {
|
||||
append(s, r.ab());
|
||||
} else {
|
||||
if (r.a() != null) {
|
||||
append(s, r.a());
|
||||
}
|
||||
if (r.b() != null) {
|
||||
append(s, r.b());
|
||||
}
|
||||
}
|
||||
// TODO skip may need to be handled
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
private static void append(StringBuilder s, JsArrayString lines) {
|
||||
for (int i = 0; i < lines.length(); i++) {
|
||||
s.append(lines.get(i)).append('\n');
|
||||
|
@@ -0,0 +1,907 @@
|
||||
// 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 impl ied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.client.diff;
|
||||
|
||||
import static com.google.gerrit.extensions.client.DiffPreferencesInfo.WHOLE_FILE_CONTEXT;
|
||||
import static java.lang.Double.POSITIVE_INFINITY;
|
||||
|
||||
import com.google.gerrit.client.Dispatcher;
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.JumpKeys;
|
||||
import com.google.gerrit.client.account.DiffPreferences;
|
||||
import com.google.gerrit.client.change.ChangeScreen;
|
||||
import com.google.gerrit.client.change.FileTable;
|
||||
import com.google.gerrit.client.changes.ChangeApi;
|
||||
import com.google.gerrit.client.changes.ChangeList;
|
||||
import com.google.gerrit.client.diff.DiffInfo.FileMeta;
|
||||
import com.google.gerrit.client.diff.LineMapper.LineOnOtherInfo;
|
||||
import com.google.gerrit.client.info.ChangeInfo;
|
||||
import com.google.gerrit.client.info.ChangeInfo.EditInfo;
|
||||
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
|
||||
import com.google.gerrit.client.info.FileInfo;
|
||||
import com.google.gerrit.client.patches.PatchUtil;
|
||||
import com.google.gerrit.client.projects.ConfigInfoCache;
|
||||
import com.google.gerrit.client.rpc.CallbackGroup;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.rpc.RestApi;
|
||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||
import com.google.gerrit.client.ui.Screen;
|
||||
import com.google.gerrit.common.PageLinks;
|
||||
import com.google.gerrit.extensions.client.ListChangesOption;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Patch;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
import com.google.gwt.core.client.Scheduler;
|
||||
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
|
||||
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.NativeEvent;
|
||||
import com.google.gwt.event.dom.client.FocusHandler;
|
||||
import com.google.gwt.event.dom.client.KeyCodes;
|
||||
import com.google.gwt.event.dom.client.KeyPressEvent;
|
||||
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.uibinder.client.UiField;
|
||||
import com.google.gwt.user.client.Window;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwtexpui.globalkey.client.GlobalKey;
|
||||
import com.google.gwtexpui.globalkey.client.KeyCommand;
|
||||
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
|
||||
import com.google.gwtexpui.globalkey.client.ShowHelpCommand;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.CodeMirror.BeforeSelectionChangeHandler;
|
||||
import net.codemirror.lib.CodeMirror.GutterClickHandler;
|
||||
import net.codemirror.lib.CodeMirror.LineHandle;
|
||||
import net.codemirror.lib.KeyMap;
|
||||
import net.codemirror.lib.Pos;
|
||||
import net.codemirror.mode.ModeInfo;
|
||||
import net.codemirror.mode.ModeInjector;
|
||||
import net.codemirror.theme.ThemeLoader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
/** Base class for SideBySide and Unified */
|
||||
abstract class DiffScreen extends Screen {
|
||||
static final KeyMap RENDER_ENTIRE_FILE_KEYMAP = KeyMap.create()
|
||||
.propagate("Ctrl-F");
|
||||
|
||||
enum FileSize {
|
||||
SMALL(0),
|
||||
LARGE(500),
|
||||
HUGE(4000);
|
||||
|
||||
final int lines;
|
||||
|
||||
FileSize(int n) {
|
||||
this.lines = n;
|
||||
}
|
||||
}
|
||||
|
||||
enum DiffScreenType {
|
||||
SIDE_BY_SIDE, UNIFIED
|
||||
}
|
||||
|
||||
private final Change.Id changeId;
|
||||
private final PatchSet.Id base;
|
||||
private final PatchSet.Id revision;
|
||||
private final String path;
|
||||
private DisplaySide startSide;
|
||||
private int startLine;
|
||||
private DiffPreferences prefs;
|
||||
private Change.Status changeStatus;
|
||||
|
||||
private HandlerRegistration resizeHandler;
|
||||
private DiffInfo diff;
|
||||
private FileSize fileSize;
|
||||
private EditInfo edit;
|
||||
|
||||
private KeyCommandSet keysNavigation;
|
||||
private KeyCommandSet keysAction;
|
||||
private KeyCommandSet keysComment;
|
||||
private List<HandlerRegistration> handlers;
|
||||
private PreferencesAction prefsAction;
|
||||
private int reloadVersionId;
|
||||
|
||||
@UiField(provided = true)
|
||||
Header header;
|
||||
|
||||
DiffScreen(
|
||||
PatchSet.Id base,
|
||||
PatchSet.Id revision,
|
||||
String path,
|
||||
DisplaySide startSide,
|
||||
int startLine,
|
||||
DiffScreenType diffScreenType) {
|
||||
this.base = base;
|
||||
this.revision = revision;
|
||||
this.changeId = revision.getParentKey();
|
||||
this.path = path;
|
||||
this.startSide = startSide;
|
||||
this.startLine = startLine;
|
||||
|
||||
prefs = DiffPreferences.create(Gerrit.getDiffPreferences());
|
||||
handlers = new ArrayList<>(6);
|
||||
keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation());
|
||||
header = new Header(keysNavigation, base, revision, path, diffScreenType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitUI() {
|
||||
super.onInitUI();
|
||||
setHeaderVisible(false);
|
||||
setWindowTitle(FileInfo.getFileName(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoad() {
|
||||
super.onLoad();
|
||||
|
||||
CallbackGroup group1 = new CallbackGroup();
|
||||
final CallbackGroup group2 = new CallbackGroup();
|
||||
|
||||
CodeMirror.initLibrary(group1.add(new AsyncCallback<Void>() {
|
||||
final AsyncCallback<Void> themeCallback = group2.addEmpty();
|
||||
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
// Load theme after CM library to ensure theme can override CSS.
|
||||
ThemeLoader.loadTheme(prefs.theme(), themeCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
}
|
||||
}));
|
||||
|
||||
DiffApi.diff(revision, path)
|
||||
.base(base)
|
||||
.wholeFile()
|
||||
.intraline(prefs.intralineDifference())
|
||||
.ignoreWhitespace(prefs.ignoreWhitespace())
|
||||
.get(group1.addFinal(new GerritCallback<DiffInfo>() {
|
||||
final AsyncCallback<Void> modeInjectorCb = group2.addEmpty();
|
||||
|
||||
@Override
|
||||
public void onSuccess(DiffInfo diffInfo) {
|
||||
diff = diffInfo;
|
||||
fileSize = bucketFileSize(diffInfo);
|
||||
|
||||
if (prefs.syntaxHighlighting()) {
|
||||
if (fileSize.compareTo(FileSize.SMALL) > 0) {
|
||||
modeInjectorCb.onSuccess(null);
|
||||
} else {
|
||||
injectMode(diffInfo, modeInjectorCb);
|
||||
}
|
||||
} else {
|
||||
modeInjectorCb.onSuccess(null);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
if (Gerrit.isSignedIn()) {
|
||||
ChangeApi.edit(changeId.get(), group2.add(
|
||||
new AsyncCallback<EditInfo>() {
|
||||
@Override
|
||||
public void onSuccess(EditInfo result) {
|
||||
edit = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
final CommentsCollections comments = new CommentsCollections();
|
||||
comments.load(base, revision, path, group2);
|
||||
|
||||
RestApi call = ChangeApi.detail(changeId.get());
|
||||
ChangeList.addOptions(call, EnumSet.of(
|
||||
ListChangesOption.ALL_REVISIONS));
|
||||
call.get(group2.add(new AsyncCallback<ChangeInfo>() {
|
||||
@Override
|
||||
public void onSuccess(ChangeInfo info) {
|
||||
changeStatus = info.status();
|
||||
info.revisions().copyKeysIntoChildren("name");
|
||||
if (edit != null) {
|
||||
edit.setName(edit.commit().commit());
|
||||
info.setEdit(edit);
|
||||
info.revisions().put(edit.name(), RevisionInfo.fromEdit(edit));
|
||||
}
|
||||
String currentRevision = info.currentRevision();
|
||||
boolean current = currentRevision != null &&
|
||||
revision.get() == info.revision(currentRevision)._number();
|
||||
JsArray<RevisionInfo> list = info.revisions().values();
|
||||
RevisionInfo.sortRevisionInfoByNumber(list);
|
||||
getDiffTable().set(prefs, list, diff, edit != null, current,
|
||||
changeStatus.isOpen(), diff.binary());
|
||||
header.setChangeInfo(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
}
|
||||
}));
|
||||
|
||||
ConfigInfoCache.get(changeId, group2.addFinal(
|
||||
getScreenLoadCallback(comments)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowView() {
|
||||
super.onShowView();
|
||||
|
||||
Window.enableScrolling(false);
|
||||
JumpKeys.enable(false);
|
||||
if (getPrefs().hideTopMenu()) {
|
||||
Gerrit.setHeaderVisible(false);
|
||||
}
|
||||
resizeHandler = Window.addResizeHandler(new ResizeHandler() {
|
||||
@Override
|
||||
public void onResize(ResizeEvent event) {
|
||||
resizeCodeMirror();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
KeyCommandSet getKeysNavigation() {
|
||||
return keysNavigation;
|
||||
}
|
||||
|
||||
KeyCommandSet getKeysAction() {
|
||||
return keysAction;
|
||||
}
|
||||
|
||||
KeyCommandSet getKeysComment() {
|
||||
return keysComment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUnload() {
|
||||
super.onUnload();
|
||||
|
||||
removeKeyHandlerRegistrations();
|
||||
if (getCommentManager() != null) {
|
||||
CallbackGroup group = new CallbackGroup();
|
||||
getCommentManager().saveAllDrafts(group);
|
||||
group.done();
|
||||
}
|
||||
if (resizeHandler != null) {
|
||||
resizeHandler.removeHandler();
|
||||
resizeHandler = null;
|
||||
}
|
||||
for (CodeMirror cm : getCms()) {
|
||||
if (cm != null) {
|
||||
cm.getWrapperElement().removeFromParent();
|
||||
}
|
||||
}
|
||||
if (prefsAction != null) {
|
||||
prefsAction.hide();
|
||||
}
|
||||
|
||||
Window.enableScrolling(true);
|
||||
Gerrit.setHeaderVisible(true);
|
||||
JumpKeys.enable(true);
|
||||
}
|
||||
|
||||
private void removeKeyHandlerRegistrations() {
|
||||
for (HandlerRegistration h : handlers) {
|
||||
h.removeHandler();
|
||||
}
|
||||
handlers.clear();
|
||||
}
|
||||
|
||||
void registerCmEvents(final CodeMirror cm) {
|
||||
cm.on("cursorActivity", updateActiveLine(cm));
|
||||
cm.on("focus", updateActiveLine(cm));
|
||||
KeyMap keyMap = KeyMap.create()
|
||||
.on("A", upToChange(true))
|
||||
.on("U", upToChange(false))
|
||||
.on("'['", header.navigate(Direction.PREV))
|
||||
.on("']'", header.navigate(Direction.NEXT))
|
||||
.on("R", header.toggleReviewed())
|
||||
.on("O", getCommentManager().toggleOpenBox(cm))
|
||||
.on("Enter", getCommentManager().toggleOpenBox(cm))
|
||||
.on("N", maybeNextVimSearch(cm))
|
||||
.on("E", openEditScreen(cm))
|
||||
.on("P", getChunkManager().diffChunkNav(cm, Direction.PREV))
|
||||
.on("Shift-M", header.reviewedAndNext())
|
||||
.on("Shift-N", maybePrevVimSearch(cm))
|
||||
.on("Shift-P", getCommentManager().commentNav(cm, Direction.PREV))
|
||||
.on("Shift-O", getCommentManager().openCloseAll(cm))
|
||||
.on("I", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
switch (getIntraLineStatus()) {
|
||||
case OFF:
|
||||
case OK:
|
||||
toggleShowIntraline();
|
||||
break;
|
||||
case FAILURE:
|
||||
case TIMEOUT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.on("','", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
prefsAction.show();
|
||||
}
|
||||
})
|
||||
.on("Shift-/", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new ShowHelpCommand().onKeyPress(null);
|
||||
}
|
||||
})
|
||||
.on("Space", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cm.vim().handleKey("<C-d>");
|
||||
}
|
||||
})
|
||||
.on("Shift-Space", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cm.vim().handleKey("<C-u>");
|
||||
}
|
||||
})
|
||||
.on("Ctrl-F", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cm.vim().handleKey("/");
|
||||
}
|
||||
})
|
||||
.on("Ctrl-A", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cm.execCommand("selectAll");
|
||||
}
|
||||
});
|
||||
if (revision.get() != 0) {
|
||||
cm.on("beforeSelectionChange", onSelectionChange(cm));
|
||||
cm.on("gutterClick", onGutterClick(cm));
|
||||
keyMap.on("C", getCommentManager().newDraftCallback(cm));
|
||||
}
|
||||
cm.addKeyMap(keyMap);
|
||||
}
|
||||
|
||||
void maybeRegisterRenderEntireFileKeyMap(CodeMirror cm) {
|
||||
if (prefs.renderEntireFile()) {
|
||||
cm.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
|
||||
}
|
||||
}
|
||||
|
||||
private BeforeSelectionChangeHandler onSelectionChange(final CodeMirror cm) {
|
||||
return new BeforeSelectionChangeHandler() {
|
||||
private InsertCommentBubble bubble;
|
||||
|
||||
@Override
|
||||
public void handle(CodeMirror cm, Pos anchor, Pos head) {
|
||||
if (anchor.equals(head)) {
|
||||
if (bubble != null) {
|
||||
bubble.setVisible(false);
|
||||
}
|
||||
return;
|
||||
} else if (bubble == null) {
|
||||
init(anchor);
|
||||
} else {
|
||||
bubble.setVisible(true);
|
||||
}
|
||||
bubble.position(cm.charCoords(head, "local"));
|
||||
}
|
||||
|
||||
private void init(Pos anchor) {
|
||||
bubble = new InsertCommentBubble(getCommentManager(), cm);
|
||||
add(bubble);
|
||||
cm.addWidget(anchor, bubble.getElement());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerKeys() {
|
||||
super.registerKeys();
|
||||
|
||||
keysNavigation.add(new UpToChangeCommand(revision, 0, 'u'));
|
||||
keysNavigation.add(
|
||||
new NoOpKeyCommand(0, 'j', PatchUtil.C.lineNext()),
|
||||
new NoOpKeyCommand(0, 'k', PatchUtil.C.linePrev()));
|
||||
keysNavigation.add(
|
||||
new NoOpKeyCommand(0, 'n', PatchUtil.C.chunkNext2()),
|
||||
new NoOpKeyCommand(0, 'p', PatchUtil.C.chunkPrev2()));
|
||||
keysNavigation.add(
|
||||
new NoOpKeyCommand(KeyCommand.M_SHIFT, 'n', PatchUtil.C.commentNext()),
|
||||
new NoOpKeyCommand(KeyCommand.M_SHIFT, 'p', PatchUtil.C.commentPrev()));
|
||||
keysNavigation.add(
|
||||
new NoOpKeyCommand(KeyCommand.M_CTRL, 'f', Gerrit.C.keySearch()));
|
||||
|
||||
keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
|
||||
keysAction.add(new NoOpKeyCommand(0, KeyCodes.KEY_ENTER,
|
||||
PatchUtil.C.expandComment()));
|
||||
keysAction.add(new NoOpKeyCommand(0, 'o', PatchUtil.C.expandComment()));
|
||||
keysAction.add(new NoOpKeyCommand(
|
||||
KeyCommand.M_SHIFT, 'o', PatchUtil.C.expandAllCommentsOnCurrentLine()));
|
||||
if (Gerrit.isSignedIn()) {
|
||||
keysAction.add(new KeyCommand(0, 'r', PatchUtil.C.toggleReviewed()) {
|
||||
@Override
|
||||
public void onKeyPress(KeyPressEvent event) {
|
||||
header.toggleReviewed().run();
|
||||
}
|
||||
});
|
||||
}
|
||||
keysAction.add(new KeyCommand(
|
||||
KeyCommand.M_SHIFT, 'm', PatchUtil.C.markAsReviewedAndGoToNext()) {
|
||||
@Override
|
||||
public void onKeyPress(KeyPressEvent event) {
|
||||
header.reviewedAndNext().run();
|
||||
}
|
||||
});
|
||||
keysAction.add(new KeyCommand(0, 'a', PatchUtil.C.openReply()) {
|
||||
@Override
|
||||
public void onKeyPress(KeyPressEvent event) {
|
||||
upToChange(true).run();
|
||||
}
|
||||
});
|
||||
keysAction.add(new KeyCommand(0, ',', PatchUtil.C.showPreferences()) {
|
||||
@Override
|
||||
public void onKeyPress(KeyPressEvent event) {
|
||||
prefsAction.show();
|
||||
}
|
||||
});
|
||||
if (getIntraLineStatus() == DiffInfo.IntraLineStatus.OFF
|
||||
|| getIntraLineStatus() == DiffInfo.IntraLineStatus.OK) {
|
||||
keysAction.add(new KeyCommand(0, 'i', PatchUtil.C.toggleIntraline()) {
|
||||
@Override
|
||||
public void onKeyPress(KeyPressEvent event) {
|
||||
toggleShowIntraline();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Gerrit.isSignedIn()) {
|
||||
keysAction.add(new NoOpKeyCommand(0, 'c', PatchUtil.C.commentInsert()));
|
||||
keysComment = new KeyCommandSet(PatchUtil.C.commentEditorSet());
|
||||
keysComment.add(new NoOpKeyCommand(KeyCommand.M_CTRL, 's',
|
||||
PatchUtil.C.commentSaveDraft()));
|
||||
keysComment.add(new NoOpKeyCommand(0, KeyCodes.KEY_ESCAPE,
|
||||
PatchUtil.C.commentCancelEdit()));
|
||||
} else {
|
||||
keysComment = null;
|
||||
}
|
||||
}
|
||||
|
||||
void registerHandlers() {
|
||||
removeKeyHandlerRegistrations();
|
||||
handlers.add(GlobalKey.add(this, keysAction));
|
||||
handlers.add(GlobalKey.add(this, keysNavigation));
|
||||
if (keysComment != null) {
|
||||
handlers.add(GlobalKey.add(this, keysComment));
|
||||
}
|
||||
handlers.add(ShowHelpCommand.addFocusHandler(getFocusHandler()));
|
||||
}
|
||||
|
||||
void setupSyntaxHighlighting() {
|
||||
if (prefs.syntaxHighlighting() && fileSize.compareTo(FileSize.SMALL) > 0) {
|
||||
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
|
||||
@Override
|
||||
public boolean execute() {
|
||||
if (prefs.syntaxHighlighting() && isAttached()) {
|
||||
setSyntaxHighlighting(prefs.syntaxHighlighting());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
||||
abstract CodeMirror newCm(
|
||||
DiffInfo.FileMeta meta, String contents, Element parent);
|
||||
|
||||
void render(DiffInfo diff) {
|
||||
header.setNoDiff(diff);
|
||||
getChunkManager().render(diff);
|
||||
}
|
||||
|
||||
abstract void setShowLineNumbers(boolean b);
|
||||
|
||||
void setShowIntraline(boolean b) {
|
||||
if (b && getIntraLineStatus() == DiffInfo.IntraLineStatus.OFF) {
|
||||
reloadDiffInfo();
|
||||
} else if (b) {
|
||||
getDiffTable().removeStyleName(Resources.I.diffTableStyle().noIntraline());
|
||||
} else {
|
||||
getDiffTable().addStyleName(Resources.I.diffTableStyle().noIntraline());
|
||||
}
|
||||
}
|
||||
|
||||
void toggleShowIntraline() {
|
||||
prefs.intralineDifference(!prefs.intralineDifference());
|
||||
setShowIntraline(prefs.intralineDifference());
|
||||
prefsAction.update();
|
||||
}
|
||||
|
||||
abstract void setSyntaxHighlighting(boolean b);
|
||||
|
||||
void setContext(final int context) {
|
||||
operation(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getSkipManager().removeAll();
|
||||
getSkipManager().render(context, diff);
|
||||
updateRenderEntireFile();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int adjustCommitMessageLine(int line) {
|
||||
/* When commit messages are shown in the diff screen they include
|
||||
a header block that looks like this:
|
||||
|
||||
1 Parent: deadbeef (Parent commit title)
|
||||
2 Author: A. U. Thor <author@example.com>
|
||||
3 AuthorDate: 2015-02-27 19:20:52 +0900
|
||||
4 Commit: A. U. Thor <author@example.com>
|
||||
5 CommitDate: 2015-02-27 19:20:52 +0900
|
||||
6 [blank line]
|
||||
7 Commit message title
|
||||
8
|
||||
9 Commit message body
|
||||
10 ...
|
||||
11 ...
|
||||
|
||||
If the commit is a merge commit, both parent commits are listed in the
|
||||
first two lines instead of a 'Parent' line:
|
||||
|
||||
1 Merge Of: deadbeef (Parent 1 commit title)
|
||||
2 beefdead (Parent 2 commit title)
|
||||
|
||||
*/
|
||||
|
||||
// Offset to compensate for header lines until the blank line
|
||||
// after 'CommitDate'
|
||||
int offset = 6;
|
||||
|
||||
// Adjust for merge commits, which have two parent lines
|
||||
if (diff.textB().startsWith("Merge")) {
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
// If the cursor is inside the header line, reset to the first line of the
|
||||
// commit message. Otherwise if the cursor is on an actual line of the commit
|
||||
// message, adjust the line number to compensate for the header lines, so the
|
||||
// focus is on the correct line.
|
||||
if (line <= offset) {
|
||||
return 1;
|
||||
} else {
|
||||
return line - offset;
|
||||
}
|
||||
}
|
||||
|
||||
private Runnable openEditScreen(final CodeMirror cm) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LineHandle handle = cm.extras().activeLine();
|
||||
int line = cm.getLineNumber(handle) + 1;
|
||||
if (Patch.COMMIT_MSG.equals(path)) {
|
||||
line = adjustCommitMessageLine(line);
|
||||
}
|
||||
String token = Dispatcher.toEditScreen(revision, path, line);
|
||||
if (!Gerrit.isSignedIn()) {
|
||||
Gerrit.doSignIn(token);
|
||||
} else {
|
||||
Gerrit.display(token);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void updateRenderEntireFile() {
|
||||
boolean entireFile = renderEntireFile();
|
||||
for (CodeMirror cm : getCms()) {
|
||||
cm.removeKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
|
||||
if (entireFile) {
|
||||
cm.addKeyMap(RENDER_ENTIRE_FILE_KEYMAP);
|
||||
}
|
||||
cm.setOption("viewportMargin", entireFile ? POSITIVE_INFINITY : 10);
|
||||
}
|
||||
}
|
||||
|
||||
void resizeCodeMirror() {
|
||||
int height = getCodeMirrorHeight();
|
||||
for (CodeMirror cm : getCms()) {
|
||||
cm.adjustHeight(height);
|
||||
}
|
||||
}
|
||||
|
||||
abstract int getCodeMirrorHeight();
|
||||
|
||||
abstract ChunkManager getChunkManager();
|
||||
|
||||
abstract CommentManager getCommentManager();
|
||||
|
||||
abstract SkipManager getSkipManager();
|
||||
|
||||
DiffPreferences getPrefs() {
|
||||
return prefs;
|
||||
}
|
||||
|
||||
PatchSet.Id getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
PatchSet.Id getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
Change.Status getChangeStatus() {
|
||||
return changeStatus;
|
||||
}
|
||||
|
||||
int getStartLine() {
|
||||
return startLine;
|
||||
}
|
||||
|
||||
void setStartLine(int startLine) {
|
||||
this.startLine = startLine;
|
||||
}
|
||||
|
||||
DisplaySide getStartSide() {
|
||||
return startSide;
|
||||
}
|
||||
|
||||
void setStartSide(DisplaySide startSide) {
|
||||
this.startSide = startSide;
|
||||
}
|
||||
|
||||
DiffInfo getDiff() {
|
||||
return diff;
|
||||
}
|
||||
|
||||
FileSize getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
PreferencesAction getPrefsAction() {
|
||||
return prefsAction;
|
||||
}
|
||||
|
||||
void setPrefsAction(PreferencesAction prefsAction) {
|
||||
this.prefsAction = prefsAction;
|
||||
}
|
||||
|
||||
abstract void operation(final Runnable apply);
|
||||
|
||||
Runnable upToChange(final boolean openReplyBox) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CallbackGroup group = new CallbackGroup();
|
||||
getCommentManager().saveAllDrafts(group);
|
||||
group.done();
|
||||
group.addListener(new GerritCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
String b = base != null ? String.valueOf(base.get()) : null;
|
||||
String rev = String.valueOf(revision.get());
|
||||
Gerrit.display(
|
||||
PageLinks.toChange(changeId, b, rev),
|
||||
new ChangeScreen(changeId, b, rev, openReplyBox,
|
||||
FileTable.Mode.REVIEW));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Runnable maybePrevVimSearch(final CodeMirror cm) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.vim().hasSearchHighlight()) {
|
||||
cm.vim().handleKey("n");
|
||||
} else {
|
||||
getCommentManager().commentNav(cm, Direction.NEXT).run();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Runnable maybeNextVimSearch(final CodeMirror cm) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.vim().hasSearchHighlight()) {
|
||||
cm.vim().handleKey("n");
|
||||
} else {
|
||||
getChunkManager().diffChunkNav(cm, Direction.NEXT).run();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
boolean renderEntireFile() {
|
||||
return prefs.renderEntireFile() && canRenderEntireFile(prefs);
|
||||
}
|
||||
|
||||
boolean canRenderEntireFile(DiffPreferences prefs) {
|
||||
// CodeMirror is too slow to layout an entire huge file.
|
||||
return fileSize.compareTo(FileSize.HUGE) < 0
|
||||
|| (prefs.context() != WHOLE_FILE_CONTEXT && prefs.context() < 100);
|
||||
}
|
||||
|
||||
DiffInfo.IntraLineStatus getIntraLineStatus() {
|
||||
return diff.intralineStatus();
|
||||
}
|
||||
|
||||
void setThemeStyles(boolean d) {
|
||||
if (d) {
|
||||
getDiffTable().addStyleName(Resources.I.diffTableStyle().dark());
|
||||
} else {
|
||||
getDiffTable().removeStyleName(Resources.I.diffTableStyle().dark());
|
||||
}
|
||||
}
|
||||
|
||||
void setShowTabs(boolean show) {
|
||||
for (CodeMirror cm : getCms()) {
|
||||
cm.extras().showTabs(show);
|
||||
}
|
||||
}
|
||||
|
||||
void setLineLength(int columns) {
|
||||
for (CodeMirror cm : getCms()) {
|
||||
cm.extras().lineLength(columns);
|
||||
}
|
||||
}
|
||||
|
||||
String getContentType(DiffInfo.FileMeta meta) {
|
||||
if (prefs.syntaxHighlighting() && meta != null
|
||||
&& meta.contentType() != null) {
|
||||
ModeInfo m = ModeInfo.findMode(meta.contentType(), path);
|
||||
return m != null ? m.mime() : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String getContentType() {
|
||||
return getContentType(diff.metaB());
|
||||
}
|
||||
|
||||
void injectMode(DiffInfo diffInfo, AsyncCallback<Void> cb) {
|
||||
new ModeInjector()
|
||||
.add(getContentType(diffInfo.metaA()))
|
||||
.add(getContentType(diffInfo.metaB()))
|
||||
.inject(cb);
|
||||
}
|
||||
|
||||
abstract void setAutoHideDiffHeader(boolean hide);
|
||||
|
||||
String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
void prefetchNextFile() {
|
||||
String nextPath = header.getNextPath();
|
||||
if (nextPath != null) {
|
||||
DiffApi.diff(revision, nextPath)
|
||||
.base(base)
|
||||
.wholeFile()
|
||||
.intraline(prefs.intralineDifference())
|
||||
.ignoreWhitespace(prefs.ignoreWhitespace())
|
||||
.get(new AsyncCallback<DiffInfo>() {
|
||||
@Override
|
||||
public void onSuccess(DiffInfo info) {
|
||||
new ModeInjector()
|
||||
.add(getContentType(info.metaA()))
|
||||
.add(getContentType(info.metaB()))
|
||||
.inject(CallbackGroup.<Void> emptyCallback());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void reloadDiffInfo() {
|
||||
final int id = ++reloadVersionId;
|
||||
DiffApi.diff(revision, path)
|
||||
.base(base)
|
||||
.wholeFile()
|
||||
.intraline(prefs.intralineDifference())
|
||||
.ignoreWhitespace(prefs.ignoreWhitespace())
|
||||
.get(new GerritCallback<DiffInfo>() {
|
||||
@Override
|
||||
public void onSuccess(DiffInfo diffInfo) {
|
||||
if (id == reloadVersionId && isAttached()) {
|
||||
diff = diffInfo;
|
||||
operation(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getSkipManager().removeAll();
|
||||
getChunkManager().reset();
|
||||
getDiffTable().scrollbar.removeDiffAnnotations();
|
||||
setShowIntraline(prefs.intralineDifference());
|
||||
render(diff);
|
||||
getSkipManager().render(prefs.context(), diff);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static FileSize bucketFileSize(DiffInfo diff) {
|
||||
FileMeta a = diff.metaA();
|
||||
FileMeta b = diff.metaB();
|
||||
FileSize[] sizes = FileSize.values();
|
||||
for (int i = sizes.length - 1; 0 <= i; i--) {
|
||||
FileSize s = sizes[i];
|
||||
if ((a != null && s.lines <= a.lines())
|
||||
|| (b != null && s.lines <= b.lines())) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return FileSize.SMALL;
|
||||
}
|
||||
|
||||
abstract Runnable updateActiveLine(CodeMirror cm);
|
||||
|
||||
private GutterClickHandler onGutterClick(final CodeMirror cm) {
|
||||
return new GutterClickHandler() {
|
||||
@Override
|
||||
public void handle(CodeMirror instance, final int line, final String gutterClass,
|
||||
NativeEvent clickEvent) {
|
||||
if (clickEvent.getButton() == NativeEvent.BUTTON_LEFT
|
||||
&& !clickEvent.getMetaKey()
|
||||
&& !clickEvent.getAltKey()
|
||||
&& !clickEvent.getCtrlKey()
|
||||
&& !clickEvent.getShiftKey()) {
|
||||
cm.setCursor(Pos.create(line));
|
||||
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
|
||||
@Override
|
||||
public void execute() {
|
||||
getCommentManager().newDraftOnGutterClick(cm, gutterClass, line + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
abstract FocusHandler getFocusHandler();
|
||||
|
||||
abstract CodeMirror[] getCms();
|
||||
|
||||
abstract CodeMirror getCmFromSide(DisplaySide side);
|
||||
|
||||
abstract DiffTable getDiffTable();
|
||||
|
||||
LineOnOtherInfo lineOnOther(DisplaySide side, int line) {
|
||||
return getChunkManager().getLineMapper().lineOnOther(side, line);
|
||||
}
|
||||
|
||||
abstract ScreenLoadCallback<ConfigInfoCache.Entry> getScreenLoadCallback(
|
||||
CommentsCollections comments);
|
||||
}
|
@@ -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.
|
||||
*/
|
||||
.range {
|
||||
background-color: #ffd500 !important;
|
||||
}
|
||||
.rangeHighlight {
|
||||
background-color: #ffff00 !important;
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
@external .diffHeader;
|
||||
.diffHeader {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #5252ad;
|
||||
}
|
||||
|
||||
.diffHeader pre {
|
||||
margin: 0 0 3px 0;
|
||||
}
|
||||
|
||||
@external .dark, .noIntraline;
|
||||
.dark {}
|
||||
.noIntraline {}
|
@@ -18,54 +18,42 @@ import com.google.gerrit.client.account.DiffPreferences;
|
||||
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
|
||||
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
import com.google.gwt.core.client.JsArrayString;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
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;
|
||||
import com.google.gwt.user.client.ui.FlowPanel;
|
||||
import com.google.gwt.user.client.ui.HTMLPanel;
|
||||
import com.google.gwt.user.client.ui.UIObject;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
|
||||
/**
|
||||
* A table with one row and two columns to hold the two CodeMirrors displaying
|
||||
* the files to be diffed.
|
||||
* Base class for SideBySideTable2 and UnifiedTable2
|
||||
*/
|
||||
class DiffTable extends Composite {
|
||||
interface Binder extends UiBinder<HTMLPanel, DiffTable> {}
|
||||
private static final Binder uiBinder = GWT.create(Binder.class);
|
||||
abstract class DiffTable extends Composite {
|
||||
static {
|
||||
Resources.I.diffTableStyle().ensureInjected();
|
||||
}
|
||||
|
||||
interface DiffTableStyle extends CssResource {
|
||||
interface Style extends CssResource {
|
||||
String fullscreen();
|
||||
String intralineBg();
|
||||
String dark();
|
||||
String diff();
|
||||
String noIntraline();
|
||||
String range();
|
||||
String rangeHighlight();
|
||||
String showLineNumbers();
|
||||
String hideA();
|
||||
String hideB();
|
||||
String padding();
|
||||
String diffHeader();
|
||||
}
|
||||
|
||||
@UiField Element cmA;
|
||||
@UiField Element cmB;
|
||||
Scrollbar scrollbar;
|
||||
@UiField Element patchSetNavRow;
|
||||
@UiField Element patchSetNavCellA;
|
||||
@UiField Element patchSetNavCellB;
|
||||
@UiField Element diffHeaderRow;
|
||||
@UiField Element diffHeaderText;
|
||||
@UiField FlowPanel widgets;
|
||||
@UiField static DiffTableStyle style;
|
||||
|
||||
@UiField(provided = true)
|
||||
PatchSetSelectBox patchSetSelectBoxA;
|
||||
@@ -73,65 +61,31 @@ class DiffTable extends Composite {
|
||||
@UiField(provided = true)
|
||||
PatchSetSelectBox patchSetSelectBoxB;
|
||||
|
||||
private SideBySide parent;
|
||||
private boolean header;
|
||||
private boolean visibleA;
|
||||
private ChangeType changeType;
|
||||
Scrollbar scrollbar;
|
||||
|
||||
DiffTable(SideBySide parent, PatchSet.Id base, PatchSet.Id revision,
|
||||
String path) {
|
||||
DiffTable(DiffScreen parent, PatchSet.Id base, PatchSet.Id revision, String path) {
|
||||
patchSetSelectBoxA = new PatchSetSelectBox(
|
||||
parent, DisplaySide.A, revision.getParentKey(), base, path);
|
||||
patchSetSelectBoxB = new PatchSetSelectBox(
|
||||
parent, DisplaySide.B, revision.getParentKey(), revision, path);
|
||||
PatchSetSelectBox.link(patchSetSelectBoxA, patchSetSelectBoxB);
|
||||
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
this.scrollbar = new Scrollbar(this);
|
||||
this.parent = parent;
|
||||
this.visibleA = true;
|
||||
}
|
||||
|
||||
boolean isVisibleA() {
|
||||
return visibleA;
|
||||
}
|
||||
|
||||
void setVisibleA(boolean show) {
|
||||
visibleA = show;
|
||||
if (show) {
|
||||
removeStyleName(style.hideA());
|
||||
parent.syncScroll(DisplaySide.B); // match B's viewport
|
||||
} else {
|
||||
addStyleName(style.hideA());
|
||||
}
|
||||
}
|
||||
|
||||
Runnable toggleA() {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setVisibleA(!isVisibleA());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void setVisibleB(boolean show) {
|
||||
if (show) {
|
||||
removeStyleName(style.hideB());
|
||||
parent.syncScroll(DisplaySide.A); // match A's viewport
|
||||
} else {
|
||||
addStyleName(style.hideB());
|
||||
}
|
||||
}
|
||||
abstract boolean isVisibleA();
|
||||
|
||||
void setHeaderVisible(boolean show) {
|
||||
DiffScreen parent = getDiffScreen();
|
||||
if (show != UIObject.isVisible(patchSetNavRow)) {
|
||||
UIObject.setVisible(patchSetNavRow, show);
|
||||
UIObject.setVisible(diffHeaderRow, show && header);
|
||||
if (show) {
|
||||
parent.header.removeStyleName(style.fullscreen());
|
||||
parent.header.removeStyleName(Resources.I.diffTableStyle().fullscreen());
|
||||
} else {
|
||||
parent.header.addStyleName(style.fullscreen());
|
||||
parent.header.addStyleName(Resources.I.diffTableStyle().fullscreen());
|
||||
}
|
||||
parent.resizeCodeMirror();
|
||||
}
|
||||
@@ -182,17 +136,11 @@ class DiffTable extends Composite {
|
||||
setHideEmptyPane(prefs.hideEmptyPane());
|
||||
}
|
||||
|
||||
void setHideEmptyPane(boolean hide) {
|
||||
if (changeType == ChangeType.ADDED) {
|
||||
setVisibleA(!hide);
|
||||
} else if (changeType == ChangeType.DELETED) {
|
||||
setVisibleB(!hide);
|
||||
}
|
||||
}
|
||||
abstract void setHideEmptyPane(boolean hide);
|
||||
|
||||
void refresh() {
|
||||
if (header) {
|
||||
CodeMirror cm = parent.getCmFromSide(DisplaySide.A);
|
||||
CodeMirror cm = getDiffScreen().getCmFromSide(DisplaySide.A);
|
||||
diffHeaderText.getStyle().setMarginLeft(
|
||||
cm.getGutterElement().getOffsetWidth(),
|
||||
Unit.PX);
|
||||
@@ -202,4 +150,6 @@ class DiffTable extends Composite {
|
||||
void add(Widget widget) {
|
||||
widgets.add(widget);
|
||||
}
|
||||
|
||||
abstract DiffScreen getDiffScreen();
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import com.google.gerrit.client.changes.ChangeApi;
|
||||
import com.google.gerrit.client.changes.ReviewInfo;
|
||||
import com.google.gerrit.client.changes.Util;
|
||||
import com.google.gerrit.client.diff.DiffInfo.Region;
|
||||
import com.google.gerrit.client.diff.DiffScreen.DiffScreenType;
|
||||
import com.google.gerrit.client.info.ChangeInfo;
|
||||
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
|
||||
import com.google.gerrit.client.info.FileInfo;
|
||||
@@ -89,6 +90,7 @@ public class Header extends Composite {
|
||||
private final PatchSet.Id base;
|
||||
private final PatchSet.Id patchSetId;
|
||||
private final String path;
|
||||
private final DiffScreenType diffScreenType;
|
||||
private boolean hasPrev;
|
||||
private boolean hasNext;
|
||||
private String nextPath;
|
||||
@@ -96,12 +98,13 @@ public class Header extends Composite {
|
||||
private ReviewedState reviewedState;
|
||||
|
||||
Header(KeyCommandSet keys, PatchSet.Id base, PatchSet.Id patchSetId,
|
||||
String path) {
|
||||
String path, DiffScreenType diffSreenType) {
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
this.keys = keys;
|
||||
this.base = base;
|
||||
this.patchSetId = patchSetId;
|
||||
this.path = path;
|
||||
this.diffScreenType = diffSreenType;
|
||||
|
||||
if (!Gerrit.isSignedIn()) {
|
||||
reviewed.getElement().getStyle().setVisibility(Visibility.HIDDEN);
|
||||
@@ -267,9 +270,9 @@ public class Header extends Composite {
|
||||
}
|
||||
|
||||
private String url(FileInfo info) {
|
||||
return info.binary()
|
||||
? Dispatcher.toUnified(base, patchSetId, info.path())
|
||||
: Dispatcher.toSideBySide(base, patchSetId, info.path());
|
||||
return diffScreenType == DiffScreenType.UNIFIED || info.binary()
|
||||
? Dispatcher.toUnified(base, patchSetId, info.path())
|
||||
: Dispatcher.toSideBySide(base, patchSetId, info.path());
|
||||
}
|
||||
|
||||
private KeyCommand setupNav(InlineHyperlink link, char key, String help, FileInfo info) {
|
||||
|
@@ -43,7 +43,7 @@ class InsertCommentBubble extends Composite {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
setVisible(false);
|
||||
commentManager.insertNewDraft(cm).run();
|
||||
commentManager.newDraftCallback(cm).run();
|
||||
}
|
||||
}, ClickEvent.getType());
|
||||
}
|
||||
|
@@ -54,7 +54,7 @@ class PatchSetSelectBox extends Composite {
|
||||
@UiField HTMLPanel linkPanel;
|
||||
@UiField BoxStyle style;
|
||||
|
||||
private SideBySide parent;
|
||||
private DiffScreen parent;
|
||||
private DisplaySide side;
|
||||
private boolean sideA;
|
||||
private String path;
|
||||
@@ -63,7 +63,7 @@ class PatchSetSelectBox extends Composite {
|
||||
private PatchSet.Id idActive;
|
||||
private PatchSetSelectBox other;
|
||||
|
||||
PatchSetSelectBox(SideBySide parent,
|
||||
PatchSetSelectBox(DiffScreen parent,
|
||||
DisplaySide side,
|
||||
Change.Id changeId,
|
||||
PatchSet.Id revision,
|
||||
@@ -143,10 +143,12 @@ class PatchSetSelectBox extends Composite {
|
||||
if (sideA) {
|
||||
assert other.idActive != null;
|
||||
}
|
||||
return new InlineHyperlink(label, Dispatcher.toSideBySide(
|
||||
sideA ? id : other.idActive,
|
||||
sideA ? other.idActive : id,
|
||||
path));
|
||||
PatchSet.Id diffBase = sideA ? id : other.idActive;
|
||||
PatchSet.Id revision = sideA ? other.idActive : id;
|
||||
|
||||
return new InlineHyperlink(label, parent instanceof SideBySide
|
||||
? Dispatcher.toSideBySide(diffBase, revision, path)
|
||||
: Dispatcher.toUnified(diffBase, revision, path));
|
||||
}
|
||||
|
||||
private Anchor createDownloadLink() {
|
||||
|
@@ -22,13 +22,13 @@ import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
|
||||
class PreferencesAction {
|
||||
private final SideBySide view;
|
||||
private final DiffScreen view;
|
||||
private final DiffPreferences prefs;
|
||||
private PopupPanel popup;
|
||||
private PreferencesBox current;
|
||||
private Widget partner;
|
||||
|
||||
PreferencesAction(SideBySide view, DiffPreferences prefs) {
|
||||
PreferencesAction(DiffScreen view, DiffPreferences prefs) {
|
||||
this.view = view;
|
||||
this.prefs = prefs;
|
||||
}
|
||||
|
@@ -58,6 +58,7 @@ import com.google.gwt.user.client.ui.PopupPanel;
|
||||
import com.google.gwt.user.client.ui.ToggleButton;
|
||||
import com.google.gwt.user.client.ui.UIObject;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.mode.ModeInfo;
|
||||
import net.codemirror.mode.ModeInjector;
|
||||
import net.codemirror.theme.ThemeLoader;
|
||||
@@ -73,7 +74,7 @@ public class PreferencesBox extends Composite {
|
||||
String dialog();
|
||||
}
|
||||
|
||||
private final SideBySide view;
|
||||
private final DiffScreen view;
|
||||
private DiffPreferences prefs;
|
||||
private int contextLastValue;
|
||||
private Timer updateContextTimer;
|
||||
@@ -107,7 +108,7 @@ public class PreferencesBox extends Composite {
|
||||
@UiField Button apply;
|
||||
@UiField Button save;
|
||||
|
||||
public PreferencesBox(SideBySide view) {
|
||||
public PreferencesBox(DiffScreen view) {
|
||||
this.view = view;
|
||||
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
@@ -181,9 +182,9 @@ public class PreferencesBox extends Composite {
|
||||
lineNumbers.setValue(prefs.showLineNumbers());
|
||||
emptyPane.setValue(!prefs.hideEmptyPane());
|
||||
if (view != null) {
|
||||
leftSide.setValue(view.diffTable.isVisibleA());
|
||||
leftSide.setValue(view.getDiffTable().isVisibleA());
|
||||
leftSide.setEnabled(!(prefs.hideEmptyPane()
|
||||
&& view.diffTable.getChangeType() == ChangeType.ADDED));
|
||||
&& view.getDiffTable().getChangeType() == ChangeType.ADDED));
|
||||
} else {
|
||||
UIObject.setVisible(leftSideLabel, false);
|
||||
leftSide.setVisible(false);
|
||||
@@ -315,8 +316,9 @@ public class PreferencesBox extends Composite {
|
||||
@Override
|
||||
public void run() {
|
||||
int v = prefs.tabSize();
|
||||
view.getCmFromSide(DisplaySide.A).setOption("tabSize", v);
|
||||
view.getCmFromSide(DisplaySide.B).setOption("tabSize", v);
|
||||
for (CodeMirror cm : view.getCms()) {
|
||||
cm.setOption("tabSize", v);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -378,21 +380,23 @@ public class PreferencesBox extends Composite {
|
||||
|
||||
@UiHandler("leftSide")
|
||||
void onLeftSide(ValueChangeEvent<Boolean> e) {
|
||||
view.diffTable.setVisibleA(e.getValue());
|
||||
if (view.getDiffTable() instanceof SideBySideTable) {
|
||||
((SideBySideTable) view.getDiffTable()).setVisibleA(e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@UiHandler("emptyPane")
|
||||
void onHideEmptyPane(ValueChangeEvent<Boolean> e) {
|
||||
prefs.hideEmptyPane(!e.getValue());
|
||||
if (view != null) {
|
||||
view.diffTable.setHideEmptyPane(prefs.hideEmptyPane());
|
||||
view.getDiffTable().setHideEmptyPane(prefs.hideEmptyPane());
|
||||
if (prefs.hideEmptyPane()) {
|
||||
if (view.diffTable.getChangeType() == ChangeType.ADDED) {
|
||||
if (view.getDiffTable().getChangeType() == ChangeType.ADDED) {
|
||||
leftSide.setValue(false);
|
||||
leftSide.setEnabled(false);
|
||||
}
|
||||
} else {
|
||||
leftSide.setValue(view.diffTable.isVisibleA());
|
||||
leftSide.setValue(view.getDiffTable().isVisibleA());
|
||||
leftSide.setEnabled(true);
|
||||
}
|
||||
}
|
||||
@@ -468,8 +472,9 @@ public class PreferencesBox extends Composite {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean s = prefs.showWhitespaceErrors();
|
||||
view.getCmFromSide(DisplaySide.A).setOption("showTrailingSpace", s);
|
||||
view.getCmFromSide(DisplaySide.B).setOption("showTrailingSpace", s);
|
||||
for (CodeMirror cm : view.getCms()) {
|
||||
cm.setOption("showTrailingSpace", s);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -158,7 +158,7 @@ class PublishedBox extends CommentBox {
|
||||
|
||||
void doReply() {
|
||||
if (!Gerrit.isSignedIn()) {
|
||||
Gerrit.doSignIn(getCommentManager().getSideBySide().getToken());
|
||||
Gerrit.doSignIn(getCommentManager().getDiffScreen().getToken());
|
||||
} else if (replyBox == null) {
|
||||
addReplyBox(false);
|
||||
} else {
|
||||
@@ -176,7 +176,7 @@ class PublishedBox extends CommentBox {
|
||||
void onQuote(ClickEvent e) {
|
||||
e.stopPropagation();
|
||||
if (!Gerrit.isSignedIn()) {
|
||||
Gerrit.doSignIn(getCommentManager().getSideBySide().getToken());
|
||||
Gerrit.doSignIn(getCommentManager().getDiffScreen().getToken());
|
||||
}
|
||||
addReplyBox(true);
|
||||
}
|
||||
@@ -185,7 +185,7 @@ class PublishedBox extends CommentBox {
|
||||
void onReplyDone(ClickEvent e) {
|
||||
e.stopPropagation();
|
||||
if (!Gerrit.isSignedIn()) {
|
||||
Gerrit.doSignIn(getCommentManager().getSideBySide().getToken());
|
||||
Gerrit.doSignIn(getCommentManager().getDiffScreen().getToken());
|
||||
} else if (replyBox == null) {
|
||||
done.setEnabled(false);
|
||||
CommentInfo input = CommentInfo.createReply(comment);
|
||||
|
@@ -24,6 +24,7 @@ interface Resources extends ClientBundle {
|
||||
|
||||
@Source("CommentBox.css") CommentBox.Style style();
|
||||
@Source("Scrollbar.css") Scrollbar.Style scrollbarStyle();
|
||||
@Source("DiffTable.css") DiffTable.Style diffTableStyle();
|
||||
|
||||
/**
|
||||
* tango icon library (public domain):
|
||||
|
@@ -20,7 +20,7 @@ import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.ScrollInfo;
|
||||
|
||||
class ScrollSynchronizer {
|
||||
private DiffTable diffTable;
|
||||
private SideBySideTable diffTable;
|
||||
private LineMapper mapper;
|
||||
private ScrollCallback active;
|
||||
private ScrollCallback callbackA;
|
||||
@@ -28,7 +28,7 @@ class ScrollSynchronizer {
|
||||
private CodeMirror cmB;
|
||||
private boolean autoHideDiffTableHeader;
|
||||
|
||||
ScrollSynchronizer(DiffTable diffTable,
|
||||
ScrollSynchronizer(SideBySideTable diffTable,
|
||||
CodeMirror cmA, CodeMirror cmB,
|
||||
LineMapper mapper) {
|
||||
this.diffTable = diffTable;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,6 @@ limitations under the License.
|
||||
</ui:style>
|
||||
<g:FlowPanel styleName='{style.sbs}'>
|
||||
<d:Header ui:field='header'/>
|
||||
<d:DiffTable ui:field='diffTable'/>
|
||||
<d:SideBySideTable ui:field='diffTable'/>
|
||||
</g:FlowPanel>
|
||||
</ui:UiBinder>
|
||||
|
@@ -0,0 +1,280 @@
|
||||
// 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 static com.google.gerrit.client.diff.DisplaySide.A;
|
||||
import static com.google.gerrit.client.diff.DisplaySide.B;
|
||||
|
||||
import com.google.gerrit.client.diff.DiffInfo.Region;
|
||||
import com.google.gerrit.client.diff.DiffInfo.Span;
|
||||
import com.google.gerrit.client.rpc.Natives;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
import com.google.gwt.core.client.JsArrayString;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.NativeEvent;
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.user.client.DOM;
|
||||
import com.google.gwt.user.client.EventListener;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.CodeMirror.LineClassWhere;
|
||||
import net.codemirror.lib.Configuration;
|
||||
import net.codemirror.lib.LineWidget;
|
||||
import net.codemirror.lib.Pos;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Colors modified regions for {@link SideBySide}. */
|
||||
class SideBySideChunkManager extends ChunkManager {
|
||||
private static final String DATA_LINES = "_cs2h";
|
||||
private static double guessedLineHeightPx = 15;
|
||||
private static final JavaScriptObject focusA = initOnClick(A);
|
||||
private static final JavaScriptObject focusB = initOnClick(B);
|
||||
private static final native JavaScriptObject initOnClick(DisplaySide s) /*-{
|
||||
return $entry(function(e){
|
||||
@com.google.gerrit.client.diff.SideBySideChunkManager::focus(
|
||||
Lcom/google/gwt/dom/client/NativeEvent;
|
||||
Lcom/google/gerrit/client/diff/DisplaySide;)(e,s)
|
||||
});
|
||||
}-*/;
|
||||
|
||||
private static void focus(NativeEvent event, DisplaySide side) {
|
||||
Element e = Element.as(event.getEventTarget());
|
||||
for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
|
||||
EventListener l = DOM.getEventListener(e);
|
||||
if (l instanceof SideBySide) {
|
||||
((SideBySide) l).getCmFromSide(side).focus();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void focusOnClick(Element e, DisplaySide side) {
|
||||
onClick(e, side == A ? focusA : focusB);
|
||||
}
|
||||
|
||||
private final SideBySide host;
|
||||
private final CodeMirror cmA;
|
||||
private final CodeMirror cmB;
|
||||
|
||||
private List<DiffChunkInfo> chunks;
|
||||
private List<LineWidget> padding;
|
||||
private List<Element> paddingDivs;
|
||||
|
||||
SideBySideChunkManager(SideBySide host,
|
||||
CodeMirror cmA,
|
||||
CodeMirror cmB,
|
||||
Scrollbar scrollbar) {
|
||||
super(scrollbar);
|
||||
|
||||
this.host = host;
|
||||
this.cmA = cmA;
|
||||
this.cmB = cmB;
|
||||
}
|
||||
|
||||
@Override
|
||||
DiffChunkInfo getFirst() {
|
||||
return !chunks.isEmpty() ? chunks.get(0) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
void reset() {
|
||||
super.reset();
|
||||
|
||||
for (LineWidget w : padding) {
|
||||
w.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void render(DiffInfo diff) {
|
||||
super.render();
|
||||
|
||||
LineMapper mapper = getLineMapper();
|
||||
|
||||
chunks = new ArrayList<>();
|
||||
padding = new ArrayList<>();
|
||||
paddingDivs = new ArrayList<>();
|
||||
|
||||
String diffColor = diff.metaA() == null || diff.metaB() == null
|
||||
? SideBySideTable.style.intralineBg()
|
||||
: SideBySideTable.style.diff();
|
||||
|
||||
for (Region current : Natives.asList(diff.content())) {
|
||||
if (current.ab() != null) {
|
||||
mapper.appendCommon(current.ab().length());
|
||||
} else if (current.skip() > 0) {
|
||||
mapper.appendCommon(current.skip());
|
||||
} else if (current.common()) {
|
||||
mapper.appendCommon(current.b().length());
|
||||
} else {
|
||||
render(current, diffColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (paddingDivs.isEmpty()) {
|
||||
paddingDivs = null;
|
||||
}
|
||||
}
|
||||
|
||||
void adjustPadding() {
|
||||
if (paddingDivs != null) {
|
||||
double h = cmB.extras().lineHeightPx();
|
||||
for (Element div : paddingDivs) {
|
||||
int lines = div.getPropertyInt(DATA_LINES);
|
||||
div.getStyle().setHeight(lines * h, Unit.PX);
|
||||
}
|
||||
for (LineWidget w : padding) {
|
||||
w.changed();
|
||||
}
|
||||
paddingDivs = null;
|
||||
guessedLineHeightPx = h;
|
||||
}
|
||||
}
|
||||
|
||||
private void render(Region region, String diffColor) {
|
||||
LineMapper mapper = getLineMapper();
|
||||
|
||||
int startA = mapper.getLineA();
|
||||
int startB = mapper.getLineB();
|
||||
|
||||
JsArrayString a = region.a();
|
||||
JsArrayString b = region.b();
|
||||
int aLen = a != null ? a.length() : 0;
|
||||
int bLen = b != null ? b.length() : 0;
|
||||
|
||||
String color = a == null || b == null
|
||||
? diffColor
|
||||
: SideBySideTable.style.intralineBg();
|
||||
|
||||
colorLines(cmA, color, startA, aLen);
|
||||
colorLines(cmB, color, startB, bLen);
|
||||
markEdit(cmA, startA, a, region.editA());
|
||||
markEdit(cmB, startB, b, region.editB());
|
||||
addPadding(cmA, startA + aLen - 1, bLen - aLen);
|
||||
addPadding(cmB, startB + bLen - 1, aLen - bLen);
|
||||
addGutterTag(region, startA, startB);
|
||||
mapper.appendReplace(aLen, bLen);
|
||||
|
||||
int endA = mapper.getLineA() - 1;
|
||||
int endB = mapper.getLineB() - 1;
|
||||
if (aLen > 0) {
|
||||
addDiffChunk(cmB, endA, aLen, bLen > 0);
|
||||
}
|
||||
if (bLen > 0) {
|
||||
addDiffChunk(cmA, endB, bLen, aLen > 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void addGutterTag(Region region, int startA, int startB) {
|
||||
Scrollbar scrollbar = getScrollbar();
|
||||
if (region.a() == null) {
|
||||
scrollbar.insert(cmB, startB, region.b().length());
|
||||
} else if (region.b() == null) {
|
||||
scrollbar.delete(cmA, cmB, startA, region.a().length());
|
||||
} else {
|
||||
scrollbar.edit(cmB, startB, region.b().length());
|
||||
}
|
||||
}
|
||||
|
||||
private void markEdit(CodeMirror cm, int startLine,
|
||||
JsArrayString lines, JsArray<Span> edits) {
|
||||
if (lines == null || edits == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
EditIterator iter = new EditIterator(lines, startLine);
|
||||
Configuration bg = Configuration.create()
|
||||
.set("className", SideBySideTable.style.intralineBg())
|
||||
.set("readOnly", true);
|
||||
|
||||
Configuration diff = Configuration.create()
|
||||
.set("className", SideBySideTable.style.diff())
|
||||
.set("readOnly", true);
|
||||
|
||||
Pos last = Pos.create(0, 0);
|
||||
for (Span span : Natives.asList(edits)) {
|
||||
Pos from = iter.advance(span.skip());
|
||||
Pos to = iter.advance(span.mark());
|
||||
if (from.line() == last.line()) {
|
||||
getMarkers().add(cm.markText(last, from, bg));
|
||||
} else {
|
||||
getMarkers().add(cm.markText(Pos.create(from.line(), 0), from, bg));
|
||||
}
|
||||
getMarkers().add(cm.markText(from, to, diff));
|
||||
last = to;
|
||||
colorLines(cm, LineClassWhere.BACKGROUND,
|
||||
SideBySideTable.style.diff(),
|
||||
from.line(), to.line());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new padding div below the given line.
|
||||
*
|
||||
* @param cm parent CodeMirror to add extra space into.
|
||||
* @param line line to put the padding below.
|
||||
* @param len number of lines to pad. Padding is inserted only if
|
||||
* {@code len >= 1}.
|
||||
*/
|
||||
private void addPadding(CodeMirror cm, int line, final int len) {
|
||||
if (0 < len) {
|
||||
Element pad = DOM.createDiv();
|
||||
pad.setClassName(SideBySideTable.style.padding());
|
||||
pad.setPropertyInt(DATA_LINES, len);
|
||||
pad.getStyle().setHeight(guessedLineHeightPx * len, Unit.PX);
|
||||
focusOnClick(pad, cm.side());
|
||||
paddingDivs.add(pad);
|
||||
padding.add(cm.addLineWidget(
|
||||
line == -1 ? 0 : line,
|
||||
pad,
|
||||
Configuration.create()
|
||||
.set("coverGutter", true)
|
||||
.set("noHScroll", true)
|
||||
.set("above", line == -1)));
|
||||
}
|
||||
}
|
||||
|
||||
private void addDiffChunk(CodeMirror cmToPad, int lineOnOther,
|
||||
int chunkSize, boolean edit) {
|
||||
chunks.add(new DiffChunkInfo(host.otherCm(cmToPad).side(),
|
||||
lineOnOther - chunkSize + 1, lineOnOther, edit));
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable diffChunkNav(final CodeMirror cm, final Direction dir) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int line = cm.extras().hasActiveLine()
|
||||
? cm.getLineNumber(cm.extras().activeLine())
|
||||
: 0;
|
||||
int res = Collections.binarySearch(
|
||||
chunks,
|
||||
new DiffChunkInfo(cm.side(), line, 0, false),
|
||||
getDiffChunkComparator());
|
||||
diffChunkNavHelper(chunks, cm, res, dir);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
int getCmLine(int line, DisplaySide side) {
|
||||
return line;
|
||||
}
|
||||
}
|
@@ -0,0 +1,128 @@
|
||||
// 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.dom.client.Element;
|
||||
import com.google.gwt.dom.client.Style.Unit;
|
||||
import com.google.gwt.user.client.DOM;
|
||||
import com.google.gwt.user.client.Timer;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
|
||||
/**
|
||||
* LineWidget attached to a CodeMirror container.
|
||||
*
|
||||
* When a comment is placed on a line a CommentWidget is created on both sides.
|
||||
* The group tracks all comment boxes on that same line, and also includes an
|
||||
* empty padding element to keep subsequent lines vertically aligned.
|
||||
*/
|
||||
class SideBySideCommentGroup extends CommentGroup {
|
||||
static void pair(SideBySideCommentGroup a, SideBySideCommentGroup b) {
|
||||
a.peer = b;
|
||||
b.peer = a;
|
||||
}
|
||||
|
||||
private final Element padding;
|
||||
private SideBySideCommentGroup peer;
|
||||
|
||||
SideBySideCommentGroup(SideBySideCommentManager manager, CodeMirror cm, DisplaySide side,
|
||||
int line) {
|
||||
super(manager, cm, side, line);
|
||||
|
||||
padding = DOM.createDiv();
|
||||
padding.setClassName(SideBySideTable.style.padding());
|
||||
SideBySideChunkManager.focusOnClick(padding, cm.side());
|
||||
getElement().appendChild(padding);
|
||||
}
|
||||
|
||||
SideBySideCommentGroup getPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
@Override
|
||||
void remove(DraftBox box) {
|
||||
super.remove(box);
|
||||
|
||||
if (0 < getBoxCount() || 0 < peer.getBoxCount()) {
|
||||
resize();
|
||||
} else {
|
||||
detach();
|
||||
peer.detach();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void init(DiffTable parent) {
|
||||
if (getLineWidget() == null && peer.getLineWidget() == null) {
|
||||
this.attach(parent);
|
||||
peer.attach(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void handleRedraw() {
|
||||
getLineWidget().onRedraw(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (canComputeHeight() && peer.canComputeHeight()) {
|
||||
if (getResizeTimer() != null) {
|
||||
getResizeTimer().cancel();
|
||||
setResizeTimer(null);
|
||||
}
|
||||
adjustPadding(SideBySideCommentGroup.this, peer);
|
||||
} else if (getResizeTimer() == null) {
|
||||
setResizeTimer(new Timer() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (canComputeHeight() && peer.canComputeHeight()) {
|
||||
cancel();
|
||||
setResizeTimer(null);
|
||||
adjustPadding(SideBySideCommentGroup.this, peer);
|
||||
}
|
||||
}
|
||||
});
|
||||
getResizeTimer().scheduleRepeating(5);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
void resize() {
|
||||
if (getLineWidget() != null) {
|
||||
adjustPadding(this, peer);
|
||||
}
|
||||
}
|
||||
|
||||
private int computeHeight() {
|
||||
if (getComments().isVisible()) {
|
||||
// Include margin-bottom: 5px from CSS class.
|
||||
return getComments().getOffsetHeight() + 5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void adjustPadding(SideBySideCommentGroup a, SideBySideCommentGroup b) {
|
||||
int apx = a.computeHeight();
|
||||
int bpx = b.computeHeight();
|
||||
int h = Math.max(apx, bpx);
|
||||
a.padding.getStyle().setHeight(Math.max(0, h - apx), Unit.PX);
|
||||
b.padding.getStyle().setHeight(Math.max(0, h - bpx), Unit.PX);
|
||||
a.getLineWidget().changed();
|
||||
b.getLineWidget().changed();
|
||||
a.updateSelection();
|
||||
b.updateSelection();
|
||||
}
|
||||
}
|
@@ -0,0 +1,408 @@
|
||||
// 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.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.changes.CommentInfo;
|
||||
import com.google.gerrit.client.patches.SkippedLine;
|
||||
import com.google.gerrit.client.rpc.Natives;
|
||||
import com.google.gerrit.client.ui.CommentLinkProcessor;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.CodeMirror.LineHandle;
|
||||
import net.codemirror.lib.Pos;
|
||||
import net.codemirror.lib.TextMarker.FromTo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/** Tracks comment widgets for {@link SideBySide}. */
|
||||
class SideBySideCommentManager extends CommentManager {
|
||||
private final SideBySide host;
|
||||
private final SortedMap<Integer, SideBySideCommentGroup> sideA;
|
||||
private final SortedMap<Integer, SideBySideCommentGroup> sideB;
|
||||
|
||||
SideBySideCommentManager(SideBySide host,
|
||||
PatchSet.Id base, PatchSet.Id revision,
|
||||
String path,
|
||||
CommentLinkProcessor clp,
|
||||
boolean open) {
|
||||
super(base, revision, path, clp, open);
|
||||
|
||||
this.host = host;
|
||||
sideA = new TreeMap<>();
|
||||
sideB = new TreeMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
SideBySide getDiffScreen() {
|
||||
return host;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setExpandAllComments(boolean b) {
|
||||
setExpandAll(b);
|
||||
for (SideBySideCommentGroup g : sideA.values()) {
|
||||
g.setOpenAll(b);
|
||||
}
|
||||
for (SideBySideCommentGroup g : sideB.values()) {
|
||||
g.setOpenAll(b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable commentNav(final CodeMirror src, final Direction dir) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Every comment appears in both side maps as a linked pair.
|
||||
// It is only necessary to search one side to find a comment
|
||||
// on either side of the editor pair.
|
||||
SortedMap<Integer, SideBySideCommentGroup> map = map(src.side());
|
||||
int line = src.extras().hasActiveLine()
|
||||
? src.getLineNumber(src.extras().activeLine()) + 1
|
||||
: 0;
|
||||
if (dir == Direction.NEXT) {
|
||||
map = map.tailMap(line + 1);
|
||||
if (map.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
line = map.firstKey();
|
||||
} else {
|
||||
map = map.headMap(line);
|
||||
if (map.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
line = map.lastKey();
|
||||
}
|
||||
|
||||
SideBySideCommentGroup g = map.get(line);
|
||||
if (g.getBoxCount() == 0) {
|
||||
g = g.getPeer();
|
||||
}
|
||||
|
||||
CodeMirror cm = g.getCm();
|
||||
double y = cm.heightAtLine(g.getLine() - 1, "local");
|
||||
cm.setCursor(Pos.create(g.getLine() - 1));
|
||||
cm.scrollToY(y - 0.5 * cm.scrollbarV().getClientHeight());
|
||||
cm.focus();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void render(CommentsCollections in, boolean expandAll) {
|
||||
if (in.publishedBase != null) {
|
||||
renderPublished(DisplaySide.A, in.publishedBase);
|
||||
}
|
||||
if (in.publishedRevision != null) {
|
||||
renderPublished(DisplaySide.B, in.publishedRevision);
|
||||
}
|
||||
if (in.draftsBase != null) {
|
||||
renderDrafts(DisplaySide.A, in.draftsBase);
|
||||
}
|
||||
if (in.draftsRevision != null) {
|
||||
renderDrafts(DisplaySide.B, in.draftsRevision);
|
||||
}
|
||||
if (expandAll) {
|
||||
setExpandAllComments(true);
|
||||
}
|
||||
for (SideBySideCommentGroup g : sideA.values()) {
|
||||
g.init(host.getDiffTable());
|
||||
}
|
||||
for (SideBySideCommentGroup g : sideB.values()) {
|
||||
g.init(host.getDiffTable());
|
||||
g.handleRedraw();
|
||||
}
|
||||
setAttached(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
void renderPublished(DisplaySide forSide, JsArray<CommentInfo> in) {
|
||||
for (CommentInfo info : Natives.asList(in)) {
|
||||
DisplaySide side = displaySide(info, forSide);
|
||||
if (side != null) {
|
||||
SideBySideCommentGroup group = group(side, info.line());
|
||||
PublishedBox box = new PublishedBox(
|
||||
group,
|
||||
getCommentLinkProcessor(),
|
||||
getPatchSetIdFromSide(side),
|
||||
info,
|
||||
isOpen());
|
||||
group.add(box);
|
||||
box.setAnnotation(host.getDiffTable().scrollbar.comment(
|
||||
host.getCmFromSide(side),
|
||||
Math.max(0, info.line() - 1)));
|
||||
getPublished().put(info.id(), box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void newDraftOnGutterClick(CodeMirror cm, String gutterClass, int line) {
|
||||
if (cm.somethingSelected()) {
|
||||
FromTo fromTo = cm.getSelectedRange();
|
||||
Pos end = fromTo.to();
|
||||
if (end.ch() == 0) {
|
||||
end.line(end.line() - 1);
|
||||
end.ch(cm.getLine(end.line()).length());
|
||||
}
|
||||
|
||||
addDraftBox(cm.side(), CommentInfo.create(
|
||||
getPath(),
|
||||
getStoredSideFromDisplaySide(cm.side()),
|
||||
line,
|
||||
CommentRange.create(fromTo))).setEdit(true);
|
||||
cm.setSelection(cm.getCursor());
|
||||
} else {
|
||||
insertNewDraft(cm.side(), line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DraftBox} at the specified line and focus it.
|
||||
*
|
||||
* @param side which side the draft will appear on.
|
||||
* @param line the line the draft will be at. Lines are 1-based. Line 0 is a
|
||||
* special case creating a file level comment.
|
||||
*/
|
||||
@Override
|
||||
void insertNewDraft(DisplaySide side, int line) {
|
||||
if (line == 0) {
|
||||
host.getSkipManager().ensureFirstLineIsVisible();
|
||||
}
|
||||
|
||||
SideBySideCommentGroup group = group(side, line);
|
||||
if (0 < group.getBoxCount()) {
|
||||
CommentBox last = group.getCommentBox(group.getBoxCount() - 1);
|
||||
if (last instanceof DraftBox) {
|
||||
((DraftBox)last).setEdit(true);
|
||||
} else {
|
||||
((PublishedBox)last).doReply();
|
||||
}
|
||||
} else {
|
||||
addDraftBox(side, CommentInfo.create(
|
||||
getPath(),
|
||||
getStoredSideFromDisplaySide(side),
|
||||
line,
|
||||
null)).setEdit(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
DraftBox addDraftBox(DisplaySide side, CommentInfo info) {
|
||||
SideBySideCommentGroup group = group(side, info.line());
|
||||
DraftBox box = new DraftBox(
|
||||
group,
|
||||
getCommentLinkProcessor(),
|
||||
getPatchSetIdFromSide(side),
|
||||
info,
|
||||
isExpandAll());
|
||||
|
||||
if (info.inReplyTo() != null) {
|
||||
PublishedBox r = getPublished().get(info.inReplyTo());
|
||||
if (r != null) {
|
||||
r.setReplyBox(box);
|
||||
}
|
||||
}
|
||||
|
||||
group.add(box);
|
||||
box.setAnnotation(host.getDiffTable().scrollbar.draft(
|
||||
host.getCmFromSide(side),
|
||||
Math.max(0, info.line() - 1)));
|
||||
return box;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<SkippedLine> splitSkips(int context, List<SkippedLine> skips) {
|
||||
if (sideB.containsKey(0)) {
|
||||
// Special case of file comment; cannot skip first line.
|
||||
for (SkippedLine skip : skips) {
|
||||
if (skip.getStartB() == 0) {
|
||||
skip.incrementStart(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is not optimal, but shouldn't be too costly in most cases.
|
||||
// Maybe rewrite after done keeping track of diff chunk positions.
|
||||
for (int boxLine : sideB.tailMap(1).keySet()) {
|
||||
List<SkippedLine> temp = new ArrayList<>(skips.size() + 2);
|
||||
for (SkippedLine skip : skips) {
|
||||
int startLine = skip.getStartB();
|
||||
int deltaBefore = boxLine - startLine;
|
||||
int deltaAfter = startLine + skip.getSize() - boxLine;
|
||||
if (deltaBefore < -context || deltaAfter < -context) {
|
||||
temp.add(skip); // Size guaranteed to be greater than 1
|
||||
} else if (deltaBefore > context && deltaAfter > context) {
|
||||
SkippedLine before = new SkippedLine(
|
||||
skip.getStartA(), skip.getStartB(),
|
||||
skip.getSize() - deltaAfter - context);
|
||||
skip.incrementStart(deltaBefore + context);
|
||||
checkAndAddSkip(temp, before);
|
||||
checkAndAddSkip(temp, skip);
|
||||
} else if (deltaAfter > context) {
|
||||
skip.incrementStart(deltaBefore + context);
|
||||
checkAndAddSkip(temp, skip);
|
||||
} else if (deltaBefore > context) {
|
||||
skip.reduceSize(deltaAfter + context);
|
||||
checkAndAddSkip(temp, skip);
|
||||
}
|
||||
}
|
||||
if (temp.isEmpty()) {
|
||||
return temp;
|
||||
}
|
||||
skips = temp;
|
||||
}
|
||||
return skips;
|
||||
}
|
||||
|
||||
private static void checkAndAddSkip(List<SkippedLine> out, SkippedLine s) {
|
||||
if (s.getSize() > 1) {
|
||||
out.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void clearLine(DisplaySide side, int line, CommentGroup group) {
|
||||
SortedMap<Integer, SideBySideCommentGroup> map = map(side);
|
||||
if (map.get(line) == group) {
|
||||
map.remove(line);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable toggleOpenBox(final CodeMirror cm) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
SideBySideCommentGroup w = map(cm.side()).get(
|
||||
cm.getLineNumber(cm.extras().activeLine()) + 1);
|
||||
if (w != null) {
|
||||
w.openCloseLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable openCloseAll(final CodeMirror cm) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
SideBySideCommentGroup w = map(cm.side()).get(
|
||||
cm.getLineNumber(cm.extras().activeLine()) + 1);
|
||||
if (w != null) {
|
||||
w.openCloseAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable newDraftCallback(final CodeMirror cm) {
|
||||
if (!Gerrit.isSignedIn()) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String token = host.getToken();
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
LineHandle handle = cm.extras().activeLine();
|
||||
int line = cm.getLineNumber(handle) + 1;
|
||||
token += "@" + (cm.side() == DisplaySide.A ? "a" : "") + line;
|
||||
}
|
||||
Gerrit.doSignIn(token);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
newDraft(cm);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void newDraft(CodeMirror cm) {
|
||||
int line = cm.getLineNumber(cm.extras().activeLine()) + 1;
|
||||
if (cm.somethingSelected()) {
|
||||
FromTo fromTo = cm.getSelectedRange();
|
||||
Pos end = fromTo.to();
|
||||
if (end.ch() == 0) {
|
||||
end.line(end.line() - 1);
|
||||
end.ch(cm.getLine(end.line()).length());
|
||||
}
|
||||
|
||||
addDraftBox(cm.side(), CommentInfo.create(
|
||||
getPath(),
|
||||
getStoredSideFromDisplaySide(cm.side()),
|
||||
line,
|
||||
CommentRange.create(fromTo))).setEdit(true);
|
||||
cm.setSelection(cm.getCursor());
|
||||
} else {
|
||||
insertNewDraft(cm.side(), line);
|
||||
}
|
||||
}
|
||||
|
||||
private SideBySideCommentGroup group(DisplaySide side, int line) {
|
||||
SideBySideCommentGroup w = map(side).get(line);
|
||||
if (w != null) {
|
||||
return w;
|
||||
}
|
||||
|
||||
int lineA, lineB;
|
||||
if (line == 0) {
|
||||
lineA = lineB = 0;
|
||||
} else if (side == DisplaySide.A) {
|
||||
lineA = line;
|
||||
lineB = host.lineOnOther(side, line - 1).getLine() + 1;
|
||||
} else {
|
||||
lineA = host.lineOnOther(side, line - 1).getLine() + 1;
|
||||
lineB = line;
|
||||
}
|
||||
|
||||
SideBySideCommentGroup a = newGroup(DisplaySide.A, lineA);
|
||||
SideBySideCommentGroup b = newGroup(DisplaySide.B, lineB);
|
||||
SideBySideCommentGroup.pair(a, b);
|
||||
|
||||
sideA.put(lineA, a);
|
||||
sideB.put(lineB, b);
|
||||
|
||||
if (isAttached()) {
|
||||
a.init(host.getDiffTable());
|
||||
b.handleRedraw();
|
||||
}
|
||||
|
||||
return side == DisplaySide.A ? a : b;
|
||||
}
|
||||
|
||||
private SideBySideCommentGroup newGroup(DisplaySide side, int line) {
|
||||
return new SideBySideCommentGroup(this, host.getCmFromSide(side), side, line);
|
||||
}
|
||||
|
||||
private SortedMap<Integer, SideBySideCommentGroup> map(DisplaySide side) {
|
||||
return side == DisplaySide.A ? sideA : sideB;
|
||||
}
|
||||
}
|
@@ -0,0 +1,217 @@
|
||||
// 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.gerrit.client.patches.PatchUtil;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
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.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.ui.Anchor;
|
||||
import com.google.gwt.user.client.ui.HTMLPanel;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.Configuration;
|
||||
import net.codemirror.lib.LineWidget;
|
||||
import net.codemirror.lib.Pos;
|
||||
import net.codemirror.lib.TextMarker;
|
||||
import net.codemirror.lib.TextMarker.FromTo;
|
||||
|
||||
/** The Widget that handles expanding of skipped lines */
|
||||
class SideBySideSkipBar extends SkipBar {
|
||||
interface Binder extends UiBinder<HTMLPanel, SideBySideSkipBar> {}
|
||||
private static final Binder uiBinder = GWT.create(Binder.class);
|
||||
private static final int NUM_ROWS_TO_EXPAND = 10;
|
||||
private static final int UP_DOWN_THRESHOLD = 30;
|
||||
|
||||
interface SkipBarStyle extends CssResource {
|
||||
String noExpand();
|
||||
}
|
||||
|
||||
@UiField(provided=true) Anchor skipNum;
|
||||
@UiField(provided=true) Anchor upArrow;
|
||||
@UiField(provided=true) Anchor downArrow;
|
||||
@UiField SkipBarStyle style;
|
||||
|
||||
private final SideBySideSkipManager manager;
|
||||
private final CodeMirror cm;
|
||||
|
||||
private LineWidget lineWidget;
|
||||
private TextMarker textMarker;
|
||||
private SideBySideSkipBar otherBar;
|
||||
|
||||
SideBySideSkipBar(SideBySideSkipManager manager, final CodeMirror cm) {
|
||||
this.manager = manager;
|
||||
this.cm = cm;
|
||||
|
||||
skipNum = new Anchor(true);
|
||||
upArrow = new Anchor(true);
|
||||
downArrow = new Anchor(true);
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
addDomHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
cm.focus();
|
||||
}
|
||||
}, ClickEvent.getType());
|
||||
}
|
||||
|
||||
void collapse(int start, int end, boolean attach) {
|
||||
if (attach) {
|
||||
boolean isNew = lineWidget == null;
|
||||
Configuration cfg = Configuration.create()
|
||||
.set("coverGutter", true)
|
||||
.set("noHScroll", true);
|
||||
if (start == 0) { // First line workaround
|
||||
lineWidget = cm.addLineWidget(end + 1, getElement(), cfg.set("above", true));
|
||||
} else {
|
||||
lineWidget = cm.addLineWidget(start - 1, getElement(), cfg);
|
||||
}
|
||||
if (isNew) {
|
||||
lineWidget.onFirstRedraw(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int w = cm.getGutterElement().getOffsetWidth();
|
||||
getElement().getStyle().setPaddingLeft(w, Unit.PX);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
textMarker = cm.markText(
|
||||
Pos.create(start, 0),
|
||||
Pos.create(end),
|
||||
Configuration.create()
|
||||
.set("collapsed", true)
|
||||
.set("inclusiveLeft", true)
|
||||
.set("inclusiveRight", true));
|
||||
|
||||
textMarker.on("beforeCursorEnter", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
expandAll();
|
||||
}
|
||||
});
|
||||
|
||||
int skipped = end - start + 1;
|
||||
if (skipped <= UP_DOWN_THRESHOLD) {
|
||||
addStyleName(style.noExpand());
|
||||
} else {
|
||||
upArrow.setHTML(PatchUtil.M.expandBefore(NUM_ROWS_TO_EXPAND));
|
||||
downArrow.setHTML(PatchUtil.M.expandAfter(NUM_ROWS_TO_EXPAND));
|
||||
}
|
||||
skipNum.setText(PatchUtil.M.patchSkipRegion(Integer
|
||||
.toString(skipped)));
|
||||
}
|
||||
|
||||
static void link(SideBySideSkipBar barA, SideBySideSkipBar barB) {
|
||||
barA.otherBar = barB;
|
||||
barB.otherBar = barA;
|
||||
}
|
||||
|
||||
private void clearMarkerAndWidget() {
|
||||
textMarker.clear();
|
||||
lineWidget.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
void expandBefore(int cnt) {
|
||||
expandSideBefore(cnt);
|
||||
|
||||
if (otherBar != null) {
|
||||
otherBar.expandSideBefore(cnt);
|
||||
}
|
||||
}
|
||||
|
||||
private void expandSideBefore(int cnt) {
|
||||
FromTo range = textMarker.find();
|
||||
int oldStart = range.from().line();
|
||||
int newStart = oldStart + cnt;
|
||||
int end = range.to().line();
|
||||
clearMarkerAndWidget();
|
||||
collapse(newStart, end, true);
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
void expandSideAll() {
|
||||
clearMarkerAndWidget();
|
||||
removeFromParent();
|
||||
}
|
||||
|
||||
private void expandAfter() {
|
||||
FromTo range = textMarker.find();
|
||||
int start = range.from().line();
|
||||
int oldEnd = range.to().line();
|
||||
int newEnd = oldEnd - NUM_ROWS_TO_EXPAND;
|
||||
boolean attach = start == 0;
|
||||
if (attach) {
|
||||
clearMarkerAndWidget();
|
||||
} else {
|
||||
textMarker.clear();
|
||||
}
|
||||
collapse(start, newEnd, attach);
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
private void updateSelection() {
|
||||
if (cm.somethingSelected()) {
|
||||
FromTo sel = cm.getSelectedRange();
|
||||
cm.setSelection(sel.from(), sel.to());
|
||||
}
|
||||
}
|
||||
|
||||
@UiHandler("skipNum")
|
||||
void onExpandAll(@SuppressWarnings("unused") ClickEvent e) {
|
||||
expandAll();
|
||||
updateSelection();
|
||||
if (otherBar != null) {
|
||||
otherBar.expandAll();
|
||||
otherBar.updateSelection();
|
||||
}
|
||||
cm.focus();
|
||||
}
|
||||
|
||||
private void expandAll() {
|
||||
expandSideAll();
|
||||
if (otherBar != null) {
|
||||
otherBar.expandSideAll();
|
||||
}
|
||||
manager.remove(this, otherBar);
|
||||
}
|
||||
|
||||
@UiHandler("upArrow")
|
||||
void onExpandBefore(@SuppressWarnings("unused") ClickEvent e) {
|
||||
expandBefore(NUM_ROWS_TO_EXPAND);
|
||||
if (otherBar != null) {
|
||||
otherBar.expandBefore(NUM_ROWS_TO_EXPAND);
|
||||
}
|
||||
cm.focus();
|
||||
}
|
||||
|
||||
@UiHandler("downArrow")
|
||||
void onExpandAfter(@SuppressWarnings("unused") ClickEvent e) {
|
||||
expandAfter();
|
||||
|
||||
if (otherBar != null) {
|
||||
otherBar.expandAfter();
|
||||
}
|
||||
cm.focus();
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
||||
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
|
||||
<ui:style gss='false' type='com.google.gerrit.client.diff.SideBySideSkipBar.SkipBarStyle'>
|
||||
.skipBar {
|
||||
background-color: #def;
|
||||
height: 1.3em;
|
||||
overflow: hidden;
|
||||
}
|
||||
.text {
|
||||
display: table;
|
||||
margin: 0 auto;
|
||||
color: #777;
|
||||
font-style: italic;
|
||||
overflow: hidden;
|
||||
}
|
||||
.anchor {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.noExpand .arrow {
|
||||
display: none;
|
||||
}
|
||||
.arrow {
|
||||
font-family: Arial Unicode MS, sans-serif;
|
||||
}
|
||||
</ui:style>
|
||||
<g:HTMLPanel addStyleNames='{style.skipBar}'>
|
||||
<div class='{style.text}'>
|
||||
<ui:msg>
|
||||
<g:Anchor ui:field='upArrow' addStyleNames='{style.arrow} {style.anchor}' />
|
||||
<g:Anchor ui:field='skipNum' addStyleNames='{style.anchor}' />
|
||||
<g:Anchor ui:field='downArrow' addStyleNames=' {style.arrow} {style.anchor}' />
|
||||
</ui:msg>
|
||||
</div>
|
||||
</g:HTMLPanel>
|
||||
</ui:UiBinder>
|
@@ -0,0 +1,85 @@
|
||||
// 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.gerrit.client.patches.SkippedLine;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Collapses common regions with {@link SideBySideSkipBar} for {@link SideBySide}. */
|
||||
class SideBySideSkipManager extends SkipManager {
|
||||
private SideBySide host;
|
||||
|
||||
SideBySideSkipManager(SideBySide host, SideBySideCommentManager commentManager) {
|
||||
super(commentManager);
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
@Override
|
||||
void render(int context, DiffInfo diff) {
|
||||
List<SkippedLine> skips = getSkippedLines(context, diff);
|
||||
|
||||
if (!skips.isEmpty()) {
|
||||
CodeMirror cmA = host.getCmFromSide(DisplaySide.A);
|
||||
CodeMirror cmB = host.getCmFromSide(DisplaySide.B);
|
||||
|
||||
Set<SkipBar> skipBars = new HashSet<>();
|
||||
setSkipBars(skipBars);
|
||||
for (SkippedLine skip : skips) {
|
||||
SideBySideSkipBar barA = newSkipBar(cmA, DisplaySide.A, skip);
|
||||
SideBySideSkipBar barB = newSkipBar(cmB, DisplaySide.B, skip);
|
||||
SideBySideSkipBar.link(barA, barB);
|
||||
skipBars.add(barA);
|
||||
skipBars.add(barB);
|
||||
|
||||
if (skip.getStartA() == 0 || skip.getStartB() == 0) {
|
||||
barA.upArrow.setVisible(false);
|
||||
barB.upArrow.setVisible(false);
|
||||
setLine0(barB);
|
||||
} else if (skip.getStartA() + skip.getSize() == getLineA()
|
||||
|| skip.getStartB() + skip.getSize() == getLineB()) {
|
||||
barA.downArrow.setVisible(false);
|
||||
barB.downArrow.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void remove(SideBySideSkipBar a, SideBySideSkipBar b) {
|
||||
Set<SkipBar> skipBars = getSkipBars();
|
||||
skipBars.remove(a);
|
||||
skipBars.remove(b);
|
||||
if (getLine0() == a || getLine0() == b) {
|
||||
setLine0(null);
|
||||
}
|
||||
if (skipBars.isEmpty()) {
|
||||
setSkipBars(null);
|
||||
}
|
||||
}
|
||||
|
||||
private SideBySideSkipBar newSkipBar(CodeMirror cm, DisplaySide side, SkippedLine skip) {
|
||||
int start = side == DisplaySide.A ? skip.getStartA() : skip.getStartB();
|
||||
int end = start + skip.getSize() - 1;
|
||||
|
||||
SideBySideSkipBar bar = new SideBySideSkipBar(this, cm);
|
||||
host.getDiffTable().add(bar);
|
||||
bar.collapse(start, end, true);
|
||||
return bar;
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
// 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.gerrit.reviewdb.client.Patch.ChangeType;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
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.HTMLPanel;
|
||||
|
||||
/**
|
||||
* A table with one row and two columns to hold the two CodeMirrors displaying
|
||||
* the files to be compared.
|
||||
*/
|
||||
class SideBySideTable extends DiffTable {
|
||||
interface Binder extends UiBinder<HTMLPanel, SideBySideTable> {}
|
||||
private static final Binder uiBinder = GWT.create(Binder.class);
|
||||
|
||||
interface DiffTableStyle extends CssResource {
|
||||
String intralineBg();
|
||||
String diff();
|
||||
String showLineNumbers();
|
||||
String hideA();
|
||||
String hideB();
|
||||
String padding();
|
||||
}
|
||||
|
||||
private SideBySide parent;
|
||||
@UiField Element cmA;
|
||||
@UiField Element cmB;
|
||||
@UiField static DiffTableStyle style;
|
||||
|
||||
private boolean visibleA;
|
||||
|
||||
SideBySideTable(SideBySide parent, PatchSet.Id base, PatchSet.Id revision,
|
||||
String path) {
|
||||
super(parent, base, revision, path);
|
||||
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
this.visibleA = true;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isVisibleA() {
|
||||
return visibleA;
|
||||
}
|
||||
|
||||
void setVisibleA(boolean show) {
|
||||
visibleA = show;
|
||||
if (show) {
|
||||
removeStyleName(style.hideA());
|
||||
parent.syncScroll(DisplaySide.B); // match B's viewport
|
||||
} else {
|
||||
addStyleName(style.hideA());
|
||||
}
|
||||
}
|
||||
|
||||
Runnable toggleA() {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setVisibleA(!isVisibleA());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void setVisibleB(boolean show) {
|
||||
if (show) {
|
||||
removeStyleName(style.hideB());
|
||||
parent.syncScroll(DisplaySide.A); // match A's viewport
|
||||
} else {
|
||||
addStyleName(style.hideB());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void setHideEmptyPane(boolean hide) {
|
||||
if (getChangeType() == ChangeType.ADDED) {
|
||||
setVisibleA(!hide);
|
||||
} else if (getChangeType() == ChangeType.DELETED) {
|
||||
setVisibleB(!hide);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
SideBySide getDiffScreen() {
|
||||
return parent;
|
||||
}
|
||||
}
|
@@ -17,17 +17,15 @@ limitations under the License.
|
||||
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
||||
xmlns:g='urn:import:com.google.gwt.user.client.ui'
|
||||
xmlns:d='urn:import:com.google.gerrit.client.diff'>
|
||||
<ui:style gss='false' type='com.google.gerrit.client.diff.DiffTable.DiffTableStyle'>
|
||||
<ui:with field='res' type='com.google.gerrit.client.diff.Resources'/>
|
||||
<ui:style gss='false' type='com.google.gerrit.client.diff.SideBySideTable.DiffTableStyle'>
|
||||
@external .CodeMirror, .CodeMirror-selectedtext;
|
||||
@external .CodeMirror-linenumber;
|
||||
@external .CodeMirror-overlayscroll-vertical, .CodeMirror-scroll;
|
||||
@external .CodeMirror-dialog-bottom;
|
||||
@external .CodeMirror-cursor;
|
||||
|
||||
.fullscreen {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
@external .dark, .noIntraline;
|
||||
|
||||
.difftable .patchSetNav,
|
||||
.difftable .CodeMirror {
|
||||
@@ -98,16 +96,7 @@ limitations under the License.
|
||||
background-color: #f7f7f7;
|
||||
line-height: 1;
|
||||
}
|
||||
.fileCommentCell {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.range {
|
||||
background-color: #ffd500 !important;
|
||||
}
|
||||
.rangeHighlight {
|
||||
background-color: #ffff00 !important;
|
||||
}
|
||||
.difftable .CodeMirror-selectedtext {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
@@ -132,15 +121,6 @@ limitations under the License.
|
||||
margin-left: 21px;
|
||||
border-left: 2px solid #d64040;
|
||||
}
|
||||
|
||||
.diff_header {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #5252ad;
|
||||
}
|
||||
.diff_header pre {
|
||||
margin: 0 0 3px 0;
|
||||
}
|
||||
</ui:style>
|
||||
<g:HTMLPanel styleName='{style.difftable}'>
|
||||
<table class='{style.table}'>
|
||||
@@ -152,7 +132,7 @@ limitations under the License.
|
||||
<d:PatchSetSelectBox ui:field='patchSetSelectBoxB' />
|
||||
</td>
|
||||
</tr>
|
||||
<tr ui:field='diffHeaderRow' class='{style.diff_header}'>
|
||||
<tr ui:field='diffHeaderRow' class='{res.diffTableStyle.diffHeader}'>
|
||||
<td colspan='2'><pre ui:field='diffHeaderText' /></td>
|
||||
</tr>
|
||||
<tr>
|
@@ -14,189 +14,9 @@
|
||||
|
||||
package com.google.gerrit.client.diff;
|
||||
|
||||
import com.google.gerrit.client.patches.PatchUtil;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
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.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.ui.Anchor;
|
||||
import com.google.gwt.user.client.ui.Composite;
|
||||
import com.google.gwt.user.client.ui.HTMLPanel;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.Configuration;
|
||||
import net.codemirror.lib.LineWidget;
|
||||
import net.codemirror.lib.Pos;
|
||||
import net.codemirror.lib.TextMarker;
|
||||
import net.codemirror.lib.TextMarker.FromTo;
|
||||
|
||||
/** The Widget that handles expanding of skipped lines */
|
||||
class SkipBar extends Composite {
|
||||
interface Binder extends UiBinder<HTMLPanel, SkipBar> {}
|
||||
private static final Binder uiBinder = GWT.create(Binder.class);
|
||||
private static final int NUM_ROWS_TO_EXPAND = 10;
|
||||
private static final int UP_DOWN_THRESHOLD = 30;
|
||||
|
||||
interface SkipBarStyle extends CssResource {
|
||||
String noExpand();
|
||||
}
|
||||
|
||||
@UiField(provided=true) Anchor skipNum;
|
||||
@UiField(provided=true) Anchor upArrow;
|
||||
@UiField(provided=true) Anchor downArrow;
|
||||
@UiField SkipBarStyle style;
|
||||
|
||||
private final SkipManager manager;
|
||||
private final CodeMirror cm;
|
||||
|
||||
private LineWidget lineWidget;
|
||||
private TextMarker textMarker;
|
||||
private SkipBar otherBar;
|
||||
|
||||
SkipBar(SkipManager manager, final CodeMirror cm) {
|
||||
this.manager = manager;
|
||||
this.cm = cm;
|
||||
|
||||
skipNum = new Anchor(true);
|
||||
upArrow = new Anchor(true);
|
||||
downArrow = new Anchor(true);
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
addDomHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
cm.focus();
|
||||
}
|
||||
}, ClickEvent.getType());
|
||||
}
|
||||
|
||||
void collapse(int start, int end, boolean attach) {
|
||||
if (attach) {
|
||||
boolean isNew = lineWidget == null;
|
||||
Configuration cfg = Configuration.create()
|
||||
.set("coverGutter", true)
|
||||
.set("noHScroll", true);
|
||||
if (start == 0) { // First line workaround
|
||||
lineWidget = cm.addLineWidget(end + 1, getElement(), cfg.set("above", true));
|
||||
} else {
|
||||
lineWidget = cm.addLineWidget(start - 1, getElement(), cfg);
|
||||
}
|
||||
if (isNew) {
|
||||
lineWidget.onFirstRedraw(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int w = cm.getGutterElement().getOffsetWidth();
|
||||
getElement().getStyle().setPaddingLeft(w, Unit.PX);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
textMarker = cm.markText(
|
||||
Pos.create(start, 0),
|
||||
Pos.create(end),
|
||||
Configuration.create()
|
||||
.set("collapsed", true)
|
||||
.set("inclusiveLeft", true)
|
||||
.set("inclusiveRight", true));
|
||||
|
||||
textMarker.on("beforeCursorEnter", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
expandAll();
|
||||
}
|
||||
});
|
||||
|
||||
int skipped = end - start + 1;
|
||||
if (skipped <= UP_DOWN_THRESHOLD) {
|
||||
addStyleName(style.noExpand());
|
||||
} else {
|
||||
upArrow.setHTML(PatchUtil.M.expandBefore(NUM_ROWS_TO_EXPAND));
|
||||
downArrow.setHTML(PatchUtil.M.expandAfter(NUM_ROWS_TO_EXPAND));
|
||||
}
|
||||
skipNum.setText(PatchUtil.M.patchSkipRegion(Integer
|
||||
.toString(skipped)));
|
||||
}
|
||||
|
||||
static void link(SkipBar barA, SkipBar barB) {
|
||||
barA.otherBar = barB;
|
||||
barB.otherBar = barA;
|
||||
}
|
||||
|
||||
private void clearMarkerAndWidget() {
|
||||
textMarker.clear();
|
||||
lineWidget.clear();
|
||||
}
|
||||
|
||||
void expandBefore(int cnt) {
|
||||
expandSideBefore(cnt);
|
||||
otherBar.expandSideBefore(cnt);
|
||||
}
|
||||
|
||||
private void expandSideBefore(int cnt) {
|
||||
FromTo range = textMarker.find();
|
||||
int oldStart = range.from().line();
|
||||
int newStart = oldStart + cnt;
|
||||
int end = range.to().line();
|
||||
clearMarkerAndWidget();
|
||||
collapse(newStart, end, true);
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
void expandSideAll() {
|
||||
clearMarkerAndWidget();
|
||||
removeFromParent();
|
||||
}
|
||||
|
||||
private void expandAfter() {
|
||||
FromTo range = textMarker.find();
|
||||
int start = range.from().line();
|
||||
int oldEnd = range.to().line();
|
||||
int newEnd = oldEnd - NUM_ROWS_TO_EXPAND;
|
||||
boolean attach = start == 0;
|
||||
if (attach) {
|
||||
clearMarkerAndWidget();
|
||||
} else {
|
||||
textMarker.clear();
|
||||
}
|
||||
collapse(start, newEnd, attach);
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
private void updateSelection() {
|
||||
if (cm.somethingSelected()) {
|
||||
FromTo sel = cm.getSelectedRange();
|
||||
cm.setSelection(sel.from(), sel.to());
|
||||
}
|
||||
}
|
||||
|
||||
@UiHandler("skipNum")
|
||||
void onExpandAll(@SuppressWarnings("unused") ClickEvent e) {
|
||||
expandAll();
|
||||
updateSelection();
|
||||
otherBar.updateSelection();
|
||||
cm.focus();
|
||||
}
|
||||
|
||||
private void expandAll() {
|
||||
expandSideAll();
|
||||
otherBar.expandSideAll();
|
||||
manager.remove(this, otherBar);
|
||||
}
|
||||
|
||||
@UiHandler("upArrow")
|
||||
void onExpandBefore(@SuppressWarnings("unused") ClickEvent e) {
|
||||
expandBefore(NUM_ROWS_TO_EXPAND);
|
||||
cm.focus();
|
||||
}
|
||||
|
||||
@UiHandler("downArrow")
|
||||
void onExpandAfter(@SuppressWarnings("unused") ClickEvent e) {
|
||||
expandAfter();
|
||||
otherBar.expandAfter();
|
||||
cm.focus();
|
||||
}
|
||||
abstract class SkipBar extends Composite {
|
||||
abstract void expandSideAll();
|
||||
abstract void expandBefore(int cnt);
|
||||
}
|
||||
|
@@ -19,34 +19,34 @@ import com.google.gerrit.client.patches.SkippedLine;
|
||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Collapses common regions with {@link SkipBar} for {@link SideBySide}. */
|
||||
class SkipManager {
|
||||
private final SideBySide host;
|
||||
private final CommentManager commentManager;
|
||||
/** Collapses common regions with {@link SideBySideSkipBar} for {@link SideBySide}
|
||||
* and {@link Unified}. */
|
||||
abstract class SkipManager {
|
||||
private Set<SkipBar> skipBars;
|
||||
private SkipBar line0;
|
||||
private CommentManager commentManager;
|
||||
private int lineA;
|
||||
private int lineB;
|
||||
|
||||
SkipManager(SideBySide host, CommentManager commentManager) {
|
||||
this.host = host;
|
||||
SkipManager(CommentManager commentManager) {
|
||||
this.commentManager = commentManager;
|
||||
}
|
||||
|
||||
void render(int context, DiffInfo diff) {
|
||||
abstract void render(int context, DiffInfo diff);
|
||||
|
||||
List<SkippedLine> getSkippedLines(int context, DiffInfo diff) {
|
||||
if (context == DiffPreferencesInfo.WHOLE_FILE_CONTEXT) {
|
||||
return;
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
lineA = 0;
|
||||
lineB = 0;
|
||||
JsArray<Region> regions = diff.content();
|
||||
List<SkippedLine> skips = new ArrayList<>();
|
||||
int lineA = 0;
|
||||
int lineB = 0;
|
||||
for (int i = 0; i < regions.length(); i++) {
|
||||
Region current = regions.get(i);
|
||||
if (current.ab() != null || current.common() || current.skip() > 0) {
|
||||
@@ -69,31 +69,7 @@ class SkipManager {
|
||||
lineB += current.b() != null ? current.b().length() : 0;
|
||||
}
|
||||
}
|
||||
skips = commentManager.splitSkips(context, skips);
|
||||
|
||||
if (!skips.isEmpty()) {
|
||||
CodeMirror cmA = host.getCmFromSide(DisplaySide.A);
|
||||
CodeMirror cmB = host.getCmFromSide(DisplaySide.B);
|
||||
|
||||
skipBars = new HashSet<>();
|
||||
for (SkippedLine skip : skips) {
|
||||
SkipBar barA = newSkipBar(cmA, DisplaySide.A, skip);
|
||||
SkipBar barB = newSkipBar(cmB, DisplaySide.B, skip);
|
||||
SkipBar.link(barA, barB);
|
||||
skipBars.add(barA);
|
||||
skipBars.add(barB);
|
||||
|
||||
if (skip.getStartA() == 0 || skip.getStartB() == 0) {
|
||||
barA.upArrow.setVisible(false);
|
||||
barB.upArrow.setVisible(false);
|
||||
line0 = barB;
|
||||
} else if (skip.getStartA() + skip.getSize() == lineA
|
||||
|| skip.getStartB() + skip.getSize() == lineB) {
|
||||
barA.downArrow.setVisible(false);
|
||||
barB.downArrow.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return commentManager.splitSkips(context, skips);
|
||||
}
|
||||
|
||||
void ensureFirstLineIsVisible() {
|
||||
@@ -113,24 +89,27 @@ class SkipManager {
|
||||
}
|
||||
}
|
||||
|
||||
void remove(SkipBar a, SkipBar b) {
|
||||
skipBars.remove(a);
|
||||
skipBars.remove(b);
|
||||
if (line0 == a || line0 == b) {
|
||||
line0 = null;
|
||||
}
|
||||
if (skipBars.isEmpty()) {
|
||||
skipBars = null;
|
||||
}
|
||||
SkipBar getLine0() {
|
||||
return line0;
|
||||
}
|
||||
|
||||
private SkipBar newSkipBar(CodeMirror cm, DisplaySide side, SkippedLine skip) {
|
||||
int start = side == DisplaySide.A ? skip.getStartA() : skip.getStartB();
|
||||
int end = start + skip.getSize() - 1;
|
||||
int getLineA() {
|
||||
return lineA;
|
||||
}
|
||||
|
||||
SkipBar bar = new SkipBar(this, cm);
|
||||
host.diffTable.add(bar);
|
||||
bar.collapse(start, end, true);
|
||||
return bar;
|
||||
int getLineB() {
|
||||
return lineB;
|
||||
}
|
||||
|
||||
void setLine0(SkipBar bar) {
|
||||
line0 = bar;
|
||||
}
|
||||
|
||||
void setSkipBars(Set<SkipBar> bars) {
|
||||
skipBars = bars;
|
||||
}
|
||||
|
||||
Set<SkipBar> getSkipBars() {
|
||||
return skipBars;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,427 @@
|
||||
// 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 static java.lang.Double.POSITIVE_INFINITY;
|
||||
|
||||
import com.google.gerrit.client.Dispatcher;
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.account.DiffPreferences;
|
||||
import com.google.gerrit.client.diff.UnifiedChunkManager.LineSidePair;
|
||||
import com.google.gerrit.client.patches.PatchUtil;
|
||||
import com.google.gerrit.client.projects.ConfigInfoCache;
|
||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||
import com.google.gerrit.client.ui.InlineHyperlink;
|
||||
import com.google.gerrit.reviewdb.client.Patch;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.core.client.JsArrayString;
|
||||
import com.google.gwt.core.client.Scheduler;
|
||||
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.NativeEvent;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.event.dom.client.FocusEvent;
|
||||
import com.google.gwt.event.dom.client.FocusHandler;
|
||||
import com.google.gwt.event.dom.client.KeyPressEvent;
|
||||
import com.google.gwt.uibinder.client.UiBinder;
|
||||
import com.google.gwt.uibinder.client.UiField;
|
||||
import com.google.gwt.user.client.Window;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwt.user.client.ui.FlowPanel;
|
||||
import com.google.gwt.user.client.ui.ImageResourceRenderer;
|
||||
import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwtexpui.globalkey.client.GlobalKey;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.CodeMirror.GutterClickHandler;
|
||||
import net.codemirror.lib.CodeMirror.LineHandle;
|
||||
import net.codemirror.lib.Configuration;
|
||||
import net.codemirror.lib.Pos;
|
||||
import net.codemirror.lib.ScrollInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Unified extends DiffScreen {
|
||||
interface Binder extends UiBinder<FlowPanel, Unified> {}
|
||||
private static final Binder uiBinder = GWT.create(Binder.class);
|
||||
|
||||
@UiField(provided = true)
|
||||
UnifiedTable diffTable;
|
||||
|
||||
private CodeMirror cm;
|
||||
|
||||
private UnifiedChunkManager chunkManager;
|
||||
private UnifiedCommentManager commentManager;
|
||||
private UnifiedSkipManager skipManager;
|
||||
|
||||
private boolean autoHideDiffTableHeader;
|
||||
|
||||
public Unified(
|
||||
PatchSet.Id base,
|
||||
PatchSet.Id revision,
|
||||
String path,
|
||||
DisplaySide startSide,
|
||||
int startLine) {
|
||||
super(base, revision, path, startSide, startLine, DiffScreenType.UNIFIED);
|
||||
|
||||
diffTable = new UnifiedTable(this, base, revision, path);
|
||||
add(uiBinder.createAndBindUi(this));
|
||||
addDomHandler(GlobalKey.STOP_PROPAGATION, KeyPressEvent.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
ScreenLoadCallback<ConfigInfoCache.Entry> getScreenLoadCallback(
|
||||
final CommentsCollections comments) {
|
||||
return new ScreenLoadCallback<ConfigInfoCache.Entry>(Unified.this) {
|
||||
@Override
|
||||
protected void preDisplay(ConfigInfoCache.Entry result) {
|
||||
commentManager = new UnifiedCommentManager(
|
||||
Unified.this,
|
||||
getBase(), getRevision(), getPath(),
|
||||
result.getCommentLinkProcessor(),
|
||||
getChangeStatus().isOpen());
|
||||
setTheme(result.getTheme());
|
||||
display(comments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowView() {
|
||||
super.onShowView();
|
||||
|
||||
operation(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
resizeCodeMirror();
|
||||
cm.refresh();
|
||||
}
|
||||
});
|
||||
setLineLength(Patch.COMMIT_MSG.equals(getPath()) ? 72 : getPrefs().lineLength());
|
||||
diffTable.refresh();
|
||||
|
||||
if (getStartLine() == 0) {
|
||||
DiffChunkInfo d = chunkManager.getFirst();
|
||||
if (d != null) {
|
||||
if (d.isEdit() && d.getSide() == DisplaySide.A) {
|
||||
setStartSide(DisplaySide.B);
|
||||
} else {
|
||||
setStartSide(d.getSide());
|
||||
}
|
||||
setStartLine(chunkManager.getCmLine(d.getStart(), d.getSide()) + 1);
|
||||
}
|
||||
}
|
||||
if (getStartSide() != null && getStartLine() > 0) {
|
||||
cm.scrollToLine(
|
||||
chunkManager.getCmLine(getStartLine() - 1, getStartSide()));
|
||||
cm.focus();
|
||||
} else {
|
||||
cm.setCursor(Pos.create(0));
|
||||
cm.focus();
|
||||
}
|
||||
if (Gerrit.isSignedIn() && getPrefs().autoReview()) {
|
||||
header.autoReview();
|
||||
}
|
||||
prefetchNextFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
void registerCmEvents(final CodeMirror cm) {
|
||||
super.registerCmEvents(cm);
|
||||
|
||||
cm.on("scroll", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ScrollInfo si = cm.getScrollInfo();
|
||||
if (autoHideDiffTableHeader) {
|
||||
updateDiffTableHeader(si);
|
||||
}
|
||||
}
|
||||
});
|
||||
maybeRegisterRenderEntireFileKeyMap(cm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerKeys() {
|
||||
super.registerKeys();
|
||||
|
||||
registerHandlers();
|
||||
}
|
||||
|
||||
@Override
|
||||
FocusHandler getFocusHandler() {
|
||||
return new FocusHandler() {
|
||||
@Override
|
||||
public void onFocus(FocusEvent event) {
|
||||
cm.focus();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void display(final CommentsCollections comments) {
|
||||
final DiffPreferences prefs = getPrefs();
|
||||
final DiffInfo diff = getDiff();
|
||||
setThemeStyles(prefs.theme().isDark());
|
||||
setShowIntraline(prefs.intralineDifference());
|
||||
// TODO: Handle showLineNumbers preference
|
||||
|
||||
cm = newCm(
|
||||
diff.metaA() == null ? diff.metaB() : diff.metaA(),
|
||||
diff.textUnified(),
|
||||
diffTable.cm);
|
||||
setShowTabs(prefs.showTabs());
|
||||
|
||||
chunkManager = new UnifiedChunkManager(this, cm, diffTable.scrollbar);
|
||||
skipManager = new UnifiedSkipManager(this, commentManager);
|
||||
|
||||
operation(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Estimate initial CM3 height, fixed up in onShowView.
|
||||
int height = Window.getClientHeight()
|
||||
- (Gerrit.getHeaderFooterHeight() + 18);
|
||||
cm.setHeight(height);
|
||||
|
||||
render(diff);
|
||||
commentManager.render(comments, prefs.expandAllComments());
|
||||
skipManager.render(prefs.context(), diff);
|
||||
}
|
||||
});
|
||||
|
||||
registerCmEvents(cm);
|
||||
|
||||
setPrefsAction(new PreferencesAction(this, prefs));
|
||||
header.init(getPrefsAction(), getSideBySideDiffLink(), diff.sideBySideWebLinks());
|
||||
setAutoHideDiffHeader(prefs.autoHideDiffTableHeader());
|
||||
|
||||
setupSyntaxHighlighting();
|
||||
}
|
||||
|
||||
private List<InlineHyperlink> getSideBySideDiffLink() {
|
||||
InlineHyperlink toSideBySideDiffLink = new InlineHyperlink();
|
||||
toSideBySideDiffLink.setHTML(
|
||||
new ImageResourceRenderer().render(Gerrit.RESOURCES.sideBySideDiff()));
|
||||
toSideBySideDiffLink.setTargetHistoryToken(
|
||||
Dispatcher.toSideBySide(getBase(), getRevision(), getPath()));
|
||||
toSideBySideDiffLink.setTitle(PatchUtil.C.sideBySideDiff());
|
||||
return Collections.singletonList(toSideBySideDiffLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
CodeMirror newCm(
|
||||
DiffInfo.FileMeta meta,
|
||||
String contents,
|
||||
Element parent) {
|
||||
DiffPreferences prefs = getPrefs();
|
||||
JsArrayString gutters = JavaScriptObject.createArray().cast();
|
||||
gutters.push(UnifiedTable.style.lineNumbersLeft());
|
||||
gutters.push(UnifiedTable.style.lineNumbersRight());
|
||||
|
||||
return CodeMirror.create(parent, Configuration.create()
|
||||
.set("cursorBlinkRate", prefs.cursorBlinkRate())
|
||||
.set("cursorHeight", 0.85)
|
||||
.set("gutters", gutters)
|
||||
.set("keyMap", "vim_ro")
|
||||
.set("lineNumbers", false)
|
||||
.set("lineWrapping", false)
|
||||
.set("matchBrackets", prefs.matchBrackets())
|
||||
.set("mode", getFileSize() == FileSize.SMALL ? getContentType(meta) : null)
|
||||
.set("readOnly", true)
|
||||
.set("scrollbarStyle", "overlay")
|
||||
.set("styleSelectedText", true)
|
||||
.set("showTrailingSpace", prefs.showWhitespaceErrors())
|
||||
.set("tabSize", prefs.tabSize())
|
||||
.set("theme", prefs.theme().name().toLowerCase())
|
||||
.set("value", meta != null ? contents : "")
|
||||
.set("viewportMargin", renderEntireFile() ? POSITIVE_INFINITY : 10));
|
||||
}
|
||||
|
||||
@Override
|
||||
void setShowLineNumbers(boolean b) {
|
||||
// TODO: Implement this
|
||||
}
|
||||
|
||||
private GutterClickHandler onGutterClick(final int cmLine) {
|
||||
return new GutterClickHandler() {
|
||||
@Override
|
||||
public void handle(CodeMirror instance, int line, String gutter,
|
||||
NativeEvent clickEvent) {
|
||||
if (clickEvent.getButton() == NativeEvent.BUTTON_LEFT
|
||||
&& !clickEvent.getMetaKey()
|
||||
&& !clickEvent.getAltKey()
|
||||
&& !clickEvent.getCtrlKey()
|
||||
&& !clickEvent.getShiftKey()) {
|
||||
cm.setCursor(Pos.create(cmLine));
|
||||
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
|
||||
@Override
|
||||
public void execute() {
|
||||
commentManager.newDraftCallback(cm).run();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
LineHandle setLineNumber(DisplaySide side, final int cmLine, int line) {
|
||||
Label gutter = new Label(String.valueOf(line));
|
||||
gutter.addClickHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
onGutterClick(cmLine);
|
||||
}
|
||||
});
|
||||
diffTable.add(gutter);
|
||||
gutter.setStyleName(UnifiedTable.style.unifiedLineNumber());
|
||||
return cm.setGutterMarker(cmLine,
|
||||
side == DisplaySide.A ? UnifiedTable.style.lineNumbersLeft()
|
||||
: UnifiedTable.style.lineNumbersRight(), gutter.getElement());
|
||||
}
|
||||
|
||||
@Override
|
||||
void setSyntaxHighlighting(boolean b) {
|
||||
final DiffInfo diff = getDiff();
|
||||
final DiffPreferences prefs = getPrefs();
|
||||
if (b) {
|
||||
injectMode(diff, new AsyncCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
if (prefs.syntaxHighlighting()) {
|
||||
cm.setOption("mode", getContentType(diff.metaA() == null
|
||||
? diff.metaB()
|
||||
: diff.metaA()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
prefs.syntaxHighlighting(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cm.setOption("mode", (String) null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void setAutoHideDiffHeader(boolean autoHide) {
|
||||
if (autoHide) {
|
||||
updateDiffTableHeader(cm.getScrollInfo());
|
||||
} else {
|
||||
diffTable.setHeaderVisible(true);
|
||||
}
|
||||
autoHideDiffTableHeader = autoHide;
|
||||
}
|
||||
|
||||
private void updateDiffTableHeader(ScrollInfo si) {
|
||||
if (si.top() == 0) {
|
||||
diffTable.setHeaderVisible(true);
|
||||
} else if (si.top() > 0.5 * si.clientHeight()) {
|
||||
diffTable.setHeaderVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable updateActiveLine(final CodeMirror cm) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// The rendering of active lines has to be deferred. Reflow
|
||||
// caused by adding and removing styles chokes Firefox when arrow
|
||||
// key (or j/k) is held down. Performance on Chrome is fine
|
||||
// without the deferral.
|
||||
//
|
||||
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
|
||||
@Override
|
||||
public void execute() {
|
||||
LineHandle handle =
|
||||
cm.getLineHandleVisualStart(cm.getCursor("end").line());
|
||||
cm.extras().activeLine(handle);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
CodeMirror getCmFromSide(DisplaySide side) {
|
||||
return cm;
|
||||
}
|
||||
|
||||
int getCmLine(int line, DisplaySide side) {
|
||||
return chunkManager.getCmLine(line, side);
|
||||
}
|
||||
|
||||
LineSidePair getLineSidePairFromCmLine(int cmLine) {
|
||||
return chunkManager.getLineSidePairFromCmLine(cmLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
void resizeCodeMirror() {
|
||||
int hdr = header.getOffsetHeight() + diffTable.getHeaderHeight();
|
||||
cm.adjustHeight(hdr);
|
||||
}
|
||||
|
||||
@Override
|
||||
void operation(final Runnable apply) {
|
||||
cm.operation(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
apply.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
int getCodeMirrorHeight() {
|
||||
int rest =
|
||||
Gerrit.getHeaderFooterHeight() + header.getOffsetHeight()
|
||||
+ diffTable.getHeaderHeight() + 5; // Estimate
|
||||
return Window.getClientHeight() - rest;
|
||||
}
|
||||
|
||||
@Override
|
||||
CodeMirror[] getCms() {
|
||||
return new CodeMirror[] {cm};
|
||||
}
|
||||
|
||||
CodeMirror getCm() {
|
||||
return cm;
|
||||
}
|
||||
|
||||
@Override
|
||||
UnifiedTable getDiffTable() {
|
||||
return diffTable;
|
||||
}
|
||||
|
||||
@Override
|
||||
UnifiedChunkManager getChunkManager() {
|
||||
return chunkManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
UnifiedCommentManager getCommentManager() {
|
||||
return commentManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
UnifiedSkipManager getSkipManager() {
|
||||
return skipManager;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
||||
xmlns:g='urn:import:com.google.gwt.user.client.ui'
|
||||
xmlns:d='urn:import:com.google.gerrit.client.diff'>
|
||||
<ui:style>
|
||||
.unified {
|
||||
margin-left: -5px;
|
||||
margin-right: -5px;
|
||||
}
|
||||
</ui:style>
|
||||
<g:FlowPanel styleName='{style.unified}'>
|
||||
<d:Header ui:field='header'/>
|
||||
<d:UnifiedTable ui:field='diffTable'/>
|
||||
</g:FlowPanel>
|
||||
</ui:UiBinder>
|
@@ -0,0 +1,316 @@
|
||||
// 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.gerrit.client.diff.DiffInfo.Region;
|
||||
import com.google.gerrit.client.diff.DiffInfo.Span;
|
||||
import com.google.gerrit.client.rpc.Natives;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
import com.google.gwt.core.client.JsArrayString;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.NativeEvent;
|
||||
import com.google.gwt.user.client.DOM;
|
||||
import com.google.gwt.user.client.EventListener;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.CodeMirror.LineClassWhere;
|
||||
import net.codemirror.lib.Configuration;
|
||||
import net.codemirror.lib.Pos;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/** Colors modified regions for {@link Unified}. */
|
||||
class UnifiedChunkManager extends ChunkManager {
|
||||
private static final JavaScriptObject focus = initOnClick();
|
||||
private static final native JavaScriptObject initOnClick() /*-{
|
||||
return $entry(function(e){
|
||||
@com.google.gerrit.client.diff.UnifiedChunkManager::focus(
|
||||
Lcom/google/gwt/dom/client/NativeEvent;)(e)
|
||||
});
|
||||
}-*/;
|
||||
|
||||
private List<UnifiedDiffChunkInfo> chunks;
|
||||
|
||||
@Override
|
||||
DiffChunkInfo getFirst() {
|
||||
return !chunks.isEmpty() ? chunks.get(0) : null;
|
||||
}
|
||||
|
||||
private static void focus(NativeEvent event) {
|
||||
Element e = Element.as(event.getEventTarget());
|
||||
for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
|
||||
EventListener l = DOM.getEventListener(e);
|
||||
if (l instanceof Unified) {
|
||||
((Unified) l).getCmFromSide(DisplaySide.A).focus();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void focusOnClick(Element e) {
|
||||
onClick(e, focus);
|
||||
}
|
||||
|
||||
private final Unified host;
|
||||
private final CodeMirror cm;
|
||||
|
||||
UnifiedChunkManager(Unified host,
|
||||
CodeMirror cm,
|
||||
Scrollbar scrollbar) {
|
||||
super(scrollbar);
|
||||
|
||||
this.host = host;
|
||||
this.cm = cm;
|
||||
}
|
||||
|
||||
@Override
|
||||
void render(DiffInfo diff) {
|
||||
super.render();
|
||||
|
||||
LineMapper mapper = getLineMapper();
|
||||
|
||||
chunks = new ArrayList<>();
|
||||
|
||||
int cmLine = 0;
|
||||
boolean useIntralineBg = diff.metaA() == null || diff.metaB() == null;
|
||||
|
||||
for (Region current : Natives.asList(diff.content())) {
|
||||
int origLineA = mapper.getLineA();
|
||||
int origLineB = mapper.getLineB();
|
||||
if (current.ab() != null) {
|
||||
int length = current.ab().length();
|
||||
mapper.appendCommon(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
host.setLineNumber(DisplaySide.A, cmLine + i, origLineA + i + 1);
|
||||
host.setLineNumber(DisplaySide.B, cmLine + i, origLineB + i + 1);
|
||||
}
|
||||
cmLine += length;
|
||||
} else if (current.skip() > 0) {
|
||||
mapper.appendCommon(current.skip());
|
||||
cmLine += current.skip(); // Maybe current.ab().length();
|
||||
} else if (current.common()) {
|
||||
mapper.appendCommon(current.b().length());
|
||||
cmLine += current.b().length();
|
||||
} else {
|
||||
cmLine += render(current, cmLine, useIntralineBg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int render(Region region, int cmLine, boolean useIntralineBg) {
|
||||
LineMapper mapper = getLineMapper();
|
||||
|
||||
int startA = mapper.getLineA();
|
||||
int startB = mapper.getLineB();
|
||||
|
||||
JsArrayString a = region.a();
|
||||
JsArrayString b = region.b();
|
||||
int aLen = a != null ? a.length() : 0;
|
||||
int bLen = b != null ? b.length() : 0;
|
||||
boolean insertOrDelete = a == null || b == null;
|
||||
|
||||
colorLines(cm,
|
||||
insertOrDelete && !useIntralineBg
|
||||
? UnifiedTable.style.diffDelete()
|
||||
: UnifiedTable.style.intralineDelete(), cmLine, aLen);
|
||||
colorLines(cm,
|
||||
insertOrDelete && !useIntralineBg
|
||||
? UnifiedTable.style.diffInsert()
|
||||
: UnifiedTable.style.intralineInsert(), cmLine + aLen,
|
||||
bLen);
|
||||
markEdit(DisplaySide.A, cmLine, a, region.editA());
|
||||
markEdit(DisplaySide.B, cmLine + aLen, b, region.editB());
|
||||
addGutterTag(region, cmLine); // TODO: verify addGutterTag
|
||||
mapper.appendReplace(aLen, bLen);
|
||||
|
||||
int endA = mapper.getLineA() - 1;
|
||||
int endB = mapper.getLineB() - 1;
|
||||
if (aLen > 0) {
|
||||
addDiffChunk(DisplaySide.A, endA, aLen, cmLine, bLen > 0);
|
||||
for (int j = 0; j < aLen; j++) {
|
||||
host.setLineNumber(DisplaySide.A, cmLine + j, startA + j + 1);
|
||||
}
|
||||
}
|
||||
if (bLen > 0) {
|
||||
addDiffChunk(DisplaySide.B, endB, bLen, cmLine + aLen, aLen > 0);
|
||||
for (int j = 0; j < bLen; j++) {
|
||||
host.setLineNumber(DisplaySide.B, cmLine + aLen + j, startB + j + 1);
|
||||
}
|
||||
}
|
||||
return aLen + bLen;
|
||||
}
|
||||
|
||||
private void addGutterTag(Region region, int cmLine) {
|
||||
Scrollbar scrollbar = getScrollbar();
|
||||
if (region.a() == null) {
|
||||
scrollbar.insert(cm, cmLine, region.b().length());
|
||||
} else if (region.b() == null) {
|
||||
scrollbar.delete(cm, cm, cmLine, region.a().length());
|
||||
} else {
|
||||
scrollbar.edit(cm, cmLine, region.b().length());
|
||||
}
|
||||
}
|
||||
|
||||
private void markEdit(DisplaySide side, int startLine,
|
||||
JsArrayString lines, JsArray<Span> edits) {
|
||||
if (lines == null || edits == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
EditIterator iter = new EditIterator(lines, startLine);
|
||||
Configuration bg = Configuration.create()
|
||||
.set("className", getIntralineBgFromSide(side))
|
||||
.set("readOnly", true);
|
||||
|
||||
Configuration diff = Configuration.create()
|
||||
.set("className", getDiffColorFromSide(side))
|
||||
.set("readOnly", true);
|
||||
|
||||
Pos last = Pos.create(0, 0);
|
||||
for (Span span : Natives.asList(edits)) {
|
||||
Pos from = iter.advance(span.skip());
|
||||
Pos to = iter.advance(span.mark());
|
||||
if (from.line() == last.line()) {
|
||||
getMarkers().add(cm.markText(last, from, bg));
|
||||
} else {
|
||||
getMarkers().add(cm.markText(Pos.create(from.line(), 0), from, bg));
|
||||
}
|
||||
getMarkers().add(cm.markText(from, to, diff));
|
||||
last = to;
|
||||
colorLines(cm, LineClassWhere.BACKGROUND,
|
||||
getDiffColorFromSide(side),
|
||||
from.line(), to.line());
|
||||
}
|
||||
}
|
||||
|
||||
private String getIntralineBgFromSide(DisplaySide side) {
|
||||
return side == DisplaySide.A ? UnifiedTable.style.intralineDelete()
|
||||
: UnifiedTable.style.intralineInsert();
|
||||
}
|
||||
|
||||
private String getDiffColorFromSide(DisplaySide side) {
|
||||
return side == DisplaySide.A ? UnifiedTable.style.diffDelete()
|
||||
: UnifiedTable.style.diffInsert();
|
||||
}
|
||||
|
||||
private void addDiffChunk(DisplaySide side, int chunkEnd, int chunkSize,
|
||||
int cmLine, boolean edit) {
|
||||
chunks.add(new UnifiedDiffChunkInfo(side, chunkEnd - chunkSize + 1, chunkEnd,
|
||||
cmLine, edit));
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable diffChunkNav(final CodeMirror cm, final Direction dir) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int line = cm.extras().hasActiveLine()
|
||||
? cm.getLineNumber(cm.extras().activeLine())
|
||||
: 0;
|
||||
int res = Collections.binarySearch(
|
||||
chunks,
|
||||
new UnifiedDiffChunkInfo(cm.side(), 0, 0, line, false),
|
||||
getDiffChunkComparatorCmLine());
|
||||
diffChunkNavHelper(chunks, cm, res, dir);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Diff chunks are ordered by their starting lines in CodeMirror */
|
||||
private Comparator<UnifiedDiffChunkInfo> getDiffChunkComparatorCmLine() {
|
||||
return new Comparator<UnifiedDiffChunkInfo>() {
|
||||
@Override
|
||||
public int compare(UnifiedDiffChunkInfo o1, UnifiedDiffChunkInfo o2) {
|
||||
return o1.getCmLine() - o2.getCmLine();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
int getCmLine(int line, DisplaySide side) {
|
||||
int res =
|
||||
Collections.binarySearch(chunks,
|
||||
new UnifiedDiffChunkInfo(
|
||||
side, line, 0, 0, false), // Dummy DiffChunkInfo
|
||||
getDiffChunkComparator());
|
||||
if (res >= 0) {
|
||||
return chunks.get(res).getCmLine();
|
||||
} else { // The line might be within a DiffChunk
|
||||
res = -res - 1;
|
||||
if (res > 0) {
|
||||
UnifiedDiffChunkInfo info = chunks.get(res - 1);
|
||||
if (info.getSide() == side && info.getStart() <= line && line <= info.getEnd()) {
|
||||
return info.getCmLine() + line - info.getStart();
|
||||
} else {
|
||||
return info.getCmLine() + (side == info.getSide()
|
||||
? line
|
||||
: getLineMapper().lineOnOther(side, line).getLine()) - info.getStart();
|
||||
}
|
||||
} else {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LineSidePair getLineSidePairFromCmLine(int cmLine) {
|
||||
int res =
|
||||
Collections.binarySearch(chunks,
|
||||
new UnifiedDiffChunkInfo(
|
||||
DisplaySide.A, 0, 0, cmLine, false), // Dummy DiffChunkInfo
|
||||
getDiffChunkComparatorCmLine());
|
||||
if (res >= 0) {
|
||||
UnifiedDiffChunkInfo info = chunks.get(res);
|
||||
return new LineSidePair(info.getStart(), info.getSide());
|
||||
} else { // The line might be within a DiffChunk
|
||||
res = -res - 1;
|
||||
if (res > 0) {
|
||||
UnifiedDiffChunkInfo info = chunks.get(res - 1);
|
||||
int delta = info.getCmLine() - 1 + info.getEnd() - info.getStart() - cmLine;
|
||||
if (delta > 0) {
|
||||
return new LineSidePair(info.getStart() + delta, info.getSide());
|
||||
} else {
|
||||
delta = cmLine - info.getCmLine();
|
||||
int result = info.getStart() + delta;
|
||||
return new LineSidePair(result, info.getSide());
|
||||
}
|
||||
} else {
|
||||
// Always return side B
|
||||
return new LineSidePair(cmLine, DisplaySide.B);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class LineSidePair {
|
||||
private int line;
|
||||
private DisplaySide side;
|
||||
|
||||
LineSidePair(int line, DisplaySide side) {
|
||||
this.line = line;
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
DisplaySide getSide() {
|
||||
return side;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
// 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.user.client.Timer;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
|
||||
/**
|
||||
* LineWidget attached to a CodeMirror container.
|
||||
*
|
||||
* When a comment is placed on a line a CommentWidget is created.
|
||||
* The group tracks all comment boxes on a line in unified diff view.
|
||||
*/
|
||||
class UnifiedCommentGroup extends CommentGroup {
|
||||
UnifiedCommentGroup(UnifiedCommentManager manager, CodeMirror cm, DisplaySide side, int line) {
|
||||
super(manager, cm, side, line);
|
||||
}
|
||||
|
||||
@Override
|
||||
void remove(DraftBox box) {
|
||||
super.remove(box);
|
||||
|
||||
if (0 < getBoxCount()) {
|
||||
resize();
|
||||
} else {
|
||||
detach();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void init(DiffTable parent) {
|
||||
if (getLineWidget() == null) {
|
||||
attach(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void handleRedraw() {
|
||||
getLineWidget().onRedraw(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (canComputeHeight()) {
|
||||
if (getResizeTimer() != null) {
|
||||
getResizeTimer().cancel();
|
||||
setResizeTimer(null);
|
||||
}
|
||||
reportHeightChange();
|
||||
} else if (getResizeTimer() == null) {
|
||||
setResizeTimer(new Timer() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (canComputeHeight()) {
|
||||
cancel();
|
||||
setResizeTimer(null);
|
||||
reportHeightChange();
|
||||
}
|
||||
}
|
||||
});
|
||||
getResizeTimer().scheduleRepeating(5);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
void resize() {
|
||||
if (getLineWidget() != null) {
|
||||
reportHeightChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void reportHeightChange() {
|
||||
getLineWidget().changed();
|
||||
updateSelection();
|
||||
}
|
||||
}
|
@@ -0,0 +1,394 @@
|
||||
// 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.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.changes.CommentInfo;
|
||||
import com.google.gerrit.client.diff.UnifiedChunkManager.LineSidePair;
|
||||
import com.google.gerrit.client.patches.SkippedLine;
|
||||
import com.google.gerrit.client.rpc.Natives;
|
||||
import com.google.gerrit.client.ui.CommentLinkProcessor;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.JsArray;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.CodeMirror.LineHandle;
|
||||
import net.codemirror.lib.Pos;
|
||||
import net.codemirror.lib.TextMarker.FromTo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/** Tracks comment widgets for {@link Unified}. */
|
||||
class UnifiedCommentManager extends CommentManager {
|
||||
private final Unified host;
|
||||
private final SortedMap<Integer, UnifiedCommentGroup> sideA;
|
||||
private final SortedMap<Integer, UnifiedCommentGroup> sideB;
|
||||
|
||||
UnifiedCommentManager(Unified host,
|
||||
PatchSet.Id base, PatchSet.Id revision,
|
||||
String path,
|
||||
CommentLinkProcessor clp,
|
||||
boolean open) {
|
||||
super(base, revision, path, clp, open);
|
||||
|
||||
this.host = host;
|
||||
sideA = new TreeMap<>();
|
||||
sideB = new TreeMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
Unified getDiffScreen() {
|
||||
return host;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setExpandAllComments(boolean b) {
|
||||
setExpandAll(b);
|
||||
for (UnifiedCommentGroup g : sideA.values()) {
|
||||
g.setOpenAll(b);
|
||||
}
|
||||
for (UnifiedCommentGroup g : sideB.values()) {
|
||||
g.setOpenAll(b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable commentNav(final CodeMirror src, final Direction dir) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SortedMap<Integer, UnifiedCommentGroup> map = map(src.side());
|
||||
int line = src.extras().hasActiveLine()
|
||||
? src.getLineNumber(src.extras().activeLine()) + 1
|
||||
: 0;
|
||||
if (dir == Direction.NEXT) {
|
||||
map = map.tailMap(line + 1);
|
||||
if (map.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
line = map.firstKey();
|
||||
} else {
|
||||
map = map.headMap(line);
|
||||
if (map.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
line = map.lastKey();
|
||||
}
|
||||
|
||||
UnifiedCommentGroup g = map.get(line);
|
||||
CodeMirror cm = g.getCm();
|
||||
double y = cm.heightAtLine(g.getLine() - 1, "local");
|
||||
cm.setCursor(Pos.create(g.getLine() - 1));
|
||||
cm.scrollToY(y - 0.5 * cm.scrollbarV().getClientHeight());
|
||||
cm.focus();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void render(CommentsCollections in, boolean expandAll) {
|
||||
if (in.publishedBase != null) {
|
||||
renderPublished(DisplaySide.A, in.publishedBase);
|
||||
}
|
||||
if (in.publishedRevision != null) {
|
||||
renderPublished(DisplaySide.B, in.publishedRevision);
|
||||
}
|
||||
if (in.draftsBase != null) {
|
||||
renderDrafts(DisplaySide.A, in.draftsBase);
|
||||
}
|
||||
if (in.draftsRevision != null) {
|
||||
renderDrafts(DisplaySide.B, in.draftsRevision);
|
||||
}
|
||||
if (expandAll) {
|
||||
setExpandAllComments(true);
|
||||
}
|
||||
for (CommentGroup g : sideA.values()) {
|
||||
g.init(host.getDiffTable());
|
||||
}
|
||||
for (CommentGroup g : sideB.values()) {
|
||||
g.init(host.getDiffTable());
|
||||
g.handleRedraw();
|
||||
}
|
||||
setAttached(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
void renderPublished(DisplaySide forSide, JsArray<CommentInfo> in) {
|
||||
for (CommentInfo info : Natives.asList(in)) {
|
||||
DisplaySide side = displaySide(info, forSide);
|
||||
if (side != null) {
|
||||
int cmLinePlusOne = host.getCmLine(info.line() - 1, side);
|
||||
UnifiedCommentGroup group = group(side, cmLinePlusOne);
|
||||
PublishedBox box = new PublishedBox(
|
||||
group,
|
||||
getCommentLinkProcessor(),
|
||||
getPatchSetIdFromSide(side),
|
||||
info,
|
||||
isOpen());
|
||||
group.add(box);
|
||||
box.setAnnotation(getDiffScreen().getDiffTable().scrollbar.comment(
|
||||
host.getCm(),
|
||||
cmLinePlusOne));
|
||||
getPublished().put(info.id(), box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void newDraftOnGutterClick(CodeMirror cm, String gutterClass, int cmLinePlusOne) {
|
||||
DisplaySide side = gutterClass.equals(UnifiedTable.style.lineNumbersLeft())
|
||||
? DisplaySide.A
|
||||
: DisplaySide.B;
|
||||
if (cm.somethingSelected()) {
|
||||
FromTo fromTo = cm.getSelectedRange();
|
||||
Pos end = fromTo.to();
|
||||
if (end.ch() == 0) {
|
||||
end.line(end.line() - 1);
|
||||
end.ch(cm.getLine(end.line()).length());
|
||||
}
|
||||
|
||||
LineSidePair pair = host.getLineSidePairFromCmLine(cmLinePlusOne - 1);
|
||||
int line = pair.getLine();
|
||||
if (pair.getSide() != side) {
|
||||
line = host.lineOnOther(pair.getSide(), line).getLine();
|
||||
}
|
||||
|
||||
addDraftBox(side, CommentInfo.create(
|
||||
getPath(),
|
||||
getStoredSideFromDisplaySide(side),
|
||||
line + 1,
|
||||
CommentRange.create(fromTo))).setEdit(true);
|
||||
cm.setSelection(cm.getCursor());
|
||||
} else {
|
||||
insertNewDraft(side, cmLinePlusOne);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DraftBox} at the specified line and focus it.
|
||||
*
|
||||
* @param side which side the draft will appear on.
|
||||
* @param cmLinePlusOne the line the draft will be at, plus one.
|
||||
* Lines are 1-based. Line 0 is a special case creating a file level comment.
|
||||
*/
|
||||
@Override
|
||||
void insertNewDraft(DisplaySide side, int cmLinePlusOne) {
|
||||
if (cmLinePlusOne == 0) {
|
||||
getDiffScreen().getSkipManager().ensureFirstLineIsVisible();
|
||||
}
|
||||
|
||||
CommentGroup group = group(side, cmLinePlusOne);
|
||||
if (0 < group.getBoxCount()) {
|
||||
CommentBox last = group.getCommentBox(group.getBoxCount() - 1);
|
||||
if (last instanceof DraftBox) {
|
||||
((DraftBox)last).setEdit(true);
|
||||
} else {
|
||||
((PublishedBox)last).doReply();
|
||||
}
|
||||
} else {
|
||||
LineSidePair pair = host.getLineSidePairFromCmLine(cmLinePlusOne - 1);
|
||||
int line = pair.getLine();
|
||||
if (pair.getSide() != side) {
|
||||
line = host.lineOnOther(pair.getSide(), line).getLine();
|
||||
}
|
||||
addDraftBox(side, CommentInfo.create(
|
||||
getPath(),
|
||||
getStoredSideFromDisplaySide(side),
|
||||
line + 1,
|
||||
null)).setEdit(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
DraftBox addDraftBox(DisplaySide side, CommentInfo info) {
|
||||
int cmLinePlusOne = host.getCmLine(info.line() - 1, side) + 1;
|
||||
UnifiedCommentGroup group = group(side, cmLinePlusOne);
|
||||
DraftBox box = new DraftBox(
|
||||
group,
|
||||
getCommentLinkProcessor(),
|
||||
getPatchSetIdFromSide(side),
|
||||
info,
|
||||
isExpandAll());
|
||||
|
||||
if (info.inReplyTo() != null) {
|
||||
PublishedBox r = getPublished().get(info.inReplyTo());
|
||||
if (r != null) {
|
||||
r.setReplyBox(box);
|
||||
}
|
||||
}
|
||||
|
||||
group.add(box);
|
||||
box.setAnnotation(getDiffScreen().getDiffTable().scrollbar.draft(
|
||||
host.getCm(),
|
||||
cmLinePlusOne));
|
||||
return box;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<SkippedLine> splitSkips(int context, List<SkippedLine> skips) {
|
||||
if (sideA.containsKey(0) || sideB.containsKey(0)) {
|
||||
// Special case of file comment; cannot skip first line.
|
||||
for (SkippedLine skip : skips) {
|
||||
if (skip.getStartA() == 0) {
|
||||
skip.incrementStart(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TreeSet<Integer> allBoxLines = new TreeSet<>(sideA.tailMap(1).keySet());
|
||||
allBoxLines.addAll(sideB.tailMap(1).keySet());
|
||||
for (int boxLine : allBoxLines) {
|
||||
List<SkippedLine> temp = new ArrayList<>(skips.size() + 2);
|
||||
for (SkippedLine skip : skips) {
|
||||
int startLine = host.getCmLine(skip.getStartA(), DisplaySide.A);
|
||||
int deltaBefore = boxLine - startLine;
|
||||
int deltaAfter = startLine + skip.getSize() - boxLine;
|
||||
if (deltaBefore < -context || deltaAfter < -context) {
|
||||
temp.add(skip); // Size guaranteed to be greater than 1
|
||||
} else if (deltaBefore > context && deltaAfter > context) {
|
||||
SkippedLine before = new SkippedLine(
|
||||
skip.getStartA(), skip.getStartB(),
|
||||
skip.getSize() - deltaAfter - context);
|
||||
skip.incrementStart(deltaBefore + context);
|
||||
checkAndAddSkip(temp, before);
|
||||
checkAndAddSkip(temp, skip);
|
||||
} else if (deltaAfter > context) {
|
||||
skip.incrementStart(deltaBefore + context);
|
||||
checkAndAddSkip(temp, skip);
|
||||
} else if (deltaBefore > context) {
|
||||
skip.reduceSize(deltaAfter + context);
|
||||
checkAndAddSkip(temp, skip);
|
||||
}
|
||||
}
|
||||
if (temp.isEmpty()) {
|
||||
return temp;
|
||||
}
|
||||
skips = temp;
|
||||
}
|
||||
return skips;
|
||||
}
|
||||
|
||||
private static void checkAndAddSkip(List<SkippedLine> out, SkippedLine s) {
|
||||
if (s.getSize() > 1) {
|
||||
out.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void clearLine(DisplaySide side, int cmLinePlusOne, CommentGroup group) {
|
||||
SortedMap<Integer, UnifiedCommentGroup> map = map(side);
|
||||
if (map.get(cmLinePlusOne) == group) {
|
||||
map.remove(cmLinePlusOne);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable toggleOpenBox(final CodeMirror cm) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
UnifiedCommentGroup w = map(cm.side()).get(
|
||||
cm.getLineNumber(cm.extras().activeLine()) + 1);
|
||||
if (w != null) {
|
||||
w.openCloseLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable openCloseAll(final CodeMirror cm) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
CommentGroup w = map(cm.side()).get(
|
||||
cm.getLineNumber(cm.extras().activeLine()) + 1);
|
||||
if (w != null) {
|
||||
w.openCloseAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
Runnable newDraftCallback(final CodeMirror cm) {
|
||||
if (!Gerrit.isSignedIn()) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String token = host.getToken();
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
LineHandle handle = cm.extras().activeLine();
|
||||
int line = cm.getLineNumber(handle) + 1;
|
||||
token += "@" + line;
|
||||
}
|
||||
Gerrit.doSignIn(token);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (cm.extras().hasActiveLine()) {
|
||||
newDraft(cm);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void newDraft(CodeMirror cm) {
|
||||
int cmLine = cm.getLineNumber(cm.extras().activeLine());
|
||||
LineSidePair pair = host.getLineSidePairFromCmLine(cmLine);
|
||||
DisplaySide side = pair.getSide();
|
||||
if (cm.somethingSelected()) {
|
||||
// TODO: Handle range comment
|
||||
} else {
|
||||
insertNewDraft(side, cmLine);
|
||||
}
|
||||
}
|
||||
|
||||
private UnifiedCommentGroup group(DisplaySide side, int cmLinePlusOne) {
|
||||
UnifiedCommentGroup w = map(side).get(cmLinePlusOne);
|
||||
if (w != null) {
|
||||
return w;
|
||||
}
|
||||
|
||||
UnifiedCommentGroup g = new UnifiedCommentGroup(this, host.getCm(), side, cmLinePlusOne);
|
||||
if (side == DisplaySide.A) {
|
||||
sideA.put(cmLinePlusOne, g);
|
||||
} else {
|
||||
sideB.put(cmLinePlusOne, g);
|
||||
}
|
||||
|
||||
if (isAttached()) {
|
||||
g.init(getDiffScreen().getDiffTable());
|
||||
g.handleRedraw();
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
private SortedMap<Integer, UnifiedCommentGroup> map(DisplaySide side) {
|
||||
return side == DisplaySide.A ? sideA : sideB;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
// 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;
|
||||
|
||||
public class UnifiedDiffChunkInfo extends DiffChunkInfo {
|
||||
|
||||
private int cmLine;
|
||||
|
||||
UnifiedDiffChunkInfo(DisplaySide side,
|
||||
int start, int end, int cmLine, boolean edit) {
|
||||
super(side, start, end, edit);
|
||||
this.cmLine = cmLine;
|
||||
}
|
||||
|
||||
int getCmLine() {
|
||||
return cmLine;
|
||||
}
|
||||
}
|
@@ -0,0 +1,193 @@
|
||||
//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.gerrit.client.patches.PatchUtil;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
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.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.ui.Anchor;
|
||||
import com.google.gwt.user.client.ui.HTMLPanel;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
import net.codemirror.lib.Configuration;
|
||||
import net.codemirror.lib.LineWidget;
|
||||
import net.codemirror.lib.Pos;
|
||||
import net.codemirror.lib.TextMarker;
|
||||
import net.codemirror.lib.TextMarker.FromTo;
|
||||
|
||||
/** The Widget that handles expanding of skipped lines */
|
||||
class UnifiedSkipBar extends SkipBar {
|
||||
interface Binder extends UiBinder<HTMLPanel, UnifiedSkipBar> {}
|
||||
private static final Binder uiBinder = GWT.create(Binder.class);
|
||||
private static final int NUM_ROWS_TO_EXPAND = 10;
|
||||
private static final int UP_DOWN_THRESHOLD = 30;
|
||||
|
||||
interface SkipBarStyle extends CssResource {
|
||||
String noExpand();
|
||||
}
|
||||
|
||||
@UiField(provided=true) Anchor skipNum;
|
||||
@UiField(provided=true) Anchor upArrow;
|
||||
@UiField(provided=true) Anchor downArrow;
|
||||
@UiField SkipBarStyle style;
|
||||
|
||||
private final UnifiedSkipManager manager;
|
||||
private final CodeMirror cm;
|
||||
|
||||
private LineWidget lineWidget;
|
||||
private TextMarker textMarker;
|
||||
|
||||
UnifiedSkipBar(UnifiedSkipManager manager, final CodeMirror cm) {
|
||||
this.manager = manager;
|
||||
this.cm = cm;
|
||||
|
||||
skipNum = new Anchor(true);
|
||||
upArrow = new Anchor(true);
|
||||
downArrow = new Anchor(true);
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
addDomHandler(new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
cm.focus();
|
||||
}
|
||||
}, ClickEvent.getType());
|
||||
}
|
||||
|
||||
void collapse(int start, int end, boolean attach) {
|
||||
if (attach) {
|
||||
boolean isNew = lineWidget == null;
|
||||
Configuration cfg = Configuration.create()
|
||||
.set("coverGutter", true)
|
||||
.set("noHScroll", true);
|
||||
if (start == 0) { // First line workaround
|
||||
lineWidget = cm.addLineWidget(end + 1, getElement(), cfg.set("above", true));
|
||||
} else {
|
||||
lineWidget = cm.addLineWidget(start - 1, getElement(), cfg);
|
||||
}
|
||||
if (isNew) {
|
||||
lineWidget.onFirstRedraw(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int w = cm.getGutterElement().getOffsetWidth();
|
||||
getElement().getStyle().setPaddingLeft(w, Unit.PX);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
textMarker = cm.markText(
|
||||
Pos.create(start, 0),
|
||||
Pos.create(end),
|
||||
Configuration.create()
|
||||
.set("collapsed", true)
|
||||
.set("inclusiveLeft", true)
|
||||
.set("inclusiveRight", true));
|
||||
|
||||
textMarker.on("beforeCursorEnter", new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
expandAll();
|
||||
}
|
||||
});
|
||||
|
||||
int skipped = end - start + 1;
|
||||
if (skipped <= UP_DOWN_THRESHOLD) {
|
||||
addStyleName(style.noExpand());
|
||||
} else {
|
||||
upArrow.setHTML(PatchUtil.M.expandBefore(NUM_ROWS_TO_EXPAND));
|
||||
downArrow.setHTML(PatchUtil.M.expandAfter(NUM_ROWS_TO_EXPAND));
|
||||
}
|
||||
skipNum.setText(PatchUtil.M.patchSkipRegion(Integer
|
||||
.toString(skipped)));
|
||||
}
|
||||
|
||||
private void clearMarkerAndWidget() {
|
||||
textMarker.clear();
|
||||
lineWidget.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
void expandBefore(int cnt) {
|
||||
expandSideBefore(cnt);
|
||||
}
|
||||
|
||||
private void expandSideBefore(int cnt) {
|
||||
FromTo range = textMarker.find();
|
||||
int oldStart = range.from().line();
|
||||
int newStart = oldStart + cnt;
|
||||
int end = range.to().line();
|
||||
clearMarkerAndWidget();
|
||||
collapse(newStart, end, true);
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
void expandSideAll() {
|
||||
clearMarkerAndWidget();
|
||||
removeFromParent();
|
||||
}
|
||||
|
||||
private void expandAfter() {
|
||||
FromTo range = textMarker.find();
|
||||
int start = range.from().line();
|
||||
int oldEnd = range.to().line();
|
||||
int newEnd = oldEnd - NUM_ROWS_TO_EXPAND;
|
||||
boolean attach = start == 0;
|
||||
if (attach) {
|
||||
clearMarkerAndWidget();
|
||||
} else {
|
||||
textMarker.clear();
|
||||
}
|
||||
collapse(start, newEnd, attach);
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
private void updateSelection() {
|
||||
if (cm.somethingSelected()) {
|
||||
FromTo sel = cm.getSelectedRange();
|
||||
cm.setSelection(sel.from(), sel.to());
|
||||
}
|
||||
}
|
||||
|
||||
@UiHandler("skipNum")
|
||||
void onExpandAll(@SuppressWarnings("unused") ClickEvent e) {
|
||||
expandAll();
|
||||
updateSelection();
|
||||
cm.focus();
|
||||
}
|
||||
|
||||
private void expandAll() {
|
||||
expandSideAll();
|
||||
manager.remove(this);
|
||||
}
|
||||
|
||||
@UiHandler("upArrow")
|
||||
void onExpandBefore(@SuppressWarnings("unused") ClickEvent e) {
|
||||
expandBefore(NUM_ROWS_TO_EXPAND);
|
||||
cm.focus();
|
||||
}
|
||||
|
||||
@UiHandler("downArrow")
|
||||
void onExpandAfter(@SuppressWarnings("unused") ClickEvent e) {
|
||||
expandAfter();
|
||||
cm.focus();
|
||||
}
|
||||
}
|
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
-->
|
||||
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
||||
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
|
||||
<ui:style gss='false' type='com.google.gerrit.client.diff.SkipBar.SkipBarStyle'>
|
||||
<ui:style gss='false' type='com.google.gerrit.client.diff.UnifiedSkipBar.SkipBarStyle'>
|
||||
.skipBar {
|
||||
background-color: #def;
|
||||
height: 1.3em;
|
@@ -0,0 +1,78 @@
|
||||
// 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.gerrit.client.patches.SkippedLine;
|
||||
|
||||
import net.codemirror.lib.CodeMirror;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Collapses common regions with {@link UnifiedSkipBar} for {@link Unified}. */
|
||||
class UnifiedSkipManager extends SkipManager {
|
||||
private Unified host;
|
||||
|
||||
UnifiedSkipManager(Unified host, UnifiedCommentManager commentManager) {
|
||||
super(commentManager);
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
@Override
|
||||
void render(int context, DiffInfo diff) {
|
||||
List<SkippedLine> skips = getSkippedLines(context, diff);
|
||||
|
||||
if (!skips.isEmpty()) {
|
||||
CodeMirror cm = host.getCm();
|
||||
|
||||
Set<SkipBar> skipBars = new HashSet<>();
|
||||
setSkipBars(skipBars);
|
||||
for (SkippedLine skip : skips) {
|
||||
UnifiedSkipBar bar = newSkipBar(cm, skip);
|
||||
skipBars.add(bar);
|
||||
|
||||
if (skip.getStartA() == 0 || skip.getStartB() == 0) {
|
||||
bar.upArrow.setVisible(false);
|
||||
setLine0(bar);
|
||||
} else if (skip.getStartA() + skip.getSize() == getLineA()
|
||||
|| skip.getStartB() + skip.getSize() == getLineB()) {
|
||||
bar.downArrow.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void remove(UnifiedSkipBar bar) {
|
||||
Set<SkipBar> skipBars = getSkipBars();
|
||||
skipBars.remove(bar);
|
||||
if (getLine0() == bar) {
|
||||
setLine0(null);
|
||||
}
|
||||
if (skipBars.isEmpty()) {
|
||||
setSkipBars(null);
|
||||
}
|
||||
}
|
||||
|
||||
private UnifiedSkipBar newSkipBar(CodeMirror cm, SkippedLine skip) {
|
||||
int start = host.getCmLine(skip.getStartA(), DisplaySide.A);
|
||||
int end = start + skip.getSize() - 1;
|
||||
|
||||
UnifiedSkipBar bar = new UnifiedSkipBar(this, cm);
|
||||
host.getDiffTable().add(bar);
|
||||
bar.collapse(start, end, true);
|
||||
return bar;
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
// 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.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
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.HTMLPanel;
|
||||
|
||||
/**
|
||||
* A table with one row and one column to hold a unified CodeMirror displaying
|
||||
* the files to be compared.
|
||||
*/
|
||||
class UnifiedTable extends DiffTable {
|
||||
interface Binder extends UiBinder<HTMLPanel, UnifiedTable> {}
|
||||
private static final Binder uiBinder = GWT.create(Binder.class);
|
||||
|
||||
interface DiffTableStyle extends CssResource {
|
||||
String intralineInsert();
|
||||
String intralineDelete();
|
||||
String diffInsert();
|
||||
String diffDelete();
|
||||
String unifiedLineNumber();
|
||||
String lineNumbersLeft();
|
||||
String lineNumbersRight();
|
||||
}
|
||||
|
||||
private Unified parent;
|
||||
@UiField Element cm;
|
||||
@UiField static DiffTableStyle style;
|
||||
|
||||
UnifiedTable(Unified parent, PatchSet.Id base, PatchSet.Id revision,
|
||||
String path) {
|
||||
super(parent, base, revision, path);
|
||||
|
||||
initWidget(uiBinder.createAndBindUi(this));
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setHideEmptyPane(boolean hide) {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isVisibleA() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
Unified getDiffScreen() {
|
||||
return parent;
|
||||
}
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
||||
xmlns:g='urn:import:com.google.gwt.user.client.ui'
|
||||
xmlns:d='urn:import:com.google.gerrit.client.diff'>
|
||||
<ui:with field='res' type='com.google.gerrit.client.diff.Resources'/>
|
||||
<ui:style gss='false' type='com.google.gerrit.client.diff.UnifiedTable.DiffTableStyle'>
|
||||
@external .CodeMirror, .CodeMirror-selectedtext;
|
||||
@external .CodeMirror-vscrollbar .CodeMirror-scroll;
|
||||
@external .CodeMirror-dialog-bottom;
|
||||
@external .CodeMirror-cursor;
|
||||
|
||||
@external .dark, .unifiedLineNumber, .noIntraline;
|
||||
|
||||
.difftable .patchSetNav,
|
||||
.difftable .CodeMirror {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.difftable .CodeMirror pre {
|
||||
overflow: hidden;
|
||||
border-right: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* Preserve space for underscores. If this changes
|
||||
* see ChunkManager.addPadding() and adjust there.
|
||||
*/
|
||||
.difftable .CodeMirror pre,
|
||||
.difftable .CodeMirror pre span {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
.table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-spacing: 0;
|
||||
}
|
||||
.table td { padding: 0 }
|
||||
|
||||
/* Hide scrollbars. */
|
||||
.difftable .CodeMirror-scroll { padding-right: 0; }
|
||||
.difftable .CodeMirror-vscrollbar { display: none !important; } */
|
||||
|
||||
.diffDelete { background-color: #faa; }
|
||||
.diffInsert { background-color: #9f9; }
|
||||
.intralineDelete { background-color: #fee; }
|
||||
.intralineInsert { background-color: #dfd; }
|
||||
.noIntraline .intralineDelete { background-color: #faa; }
|
||||
.noIntraline .intralineInsert { background-color: #9f9; }
|
||||
|
||||
.dark .diffDelete { background-color: #400; }
|
||||
.dark .diffInsert { background-color: #444; }
|
||||
.dark .intralineDelete { background-color: #888; }
|
||||
.dark .intralineInsert { background-color: #bbb; }
|
||||
.dark .noIntraline .intralineDelete { background-color: #400; }
|
||||
.dark .noIntraline .intralineInsert { background-color: #444; }
|
||||
|
||||
.patchSetNav, .diff_header {
|
||||
background-color: #f7f7f7;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.difftable .CodeMirror-selectedtext {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
.difftable .CodeMirror div.CodeMirror-cursor {
|
||||
border-left: 2px solid black;
|
||||
}
|
||||
.difftable .CodeMirror-dialog-bottom {
|
||||
border-top: 0;
|
||||
border-left: 1px solid #000;
|
||||
border-bottom: 1px solid #000;
|
||||
background-color: #f7f7f7;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.lineNumbersLeft, .lineNumbersRight {
|
||||
min-width: 20px;
|
||||
width: 3em; /* TODO: This needs to be set based on number of lines */
|
||||
}
|
||||
.lineNumbersLeft {
|
||||
border-right: 1px solid #ddd;
|
||||
}
|
||||
.unifiedLineNumber {
|
||||
cursor: pointer;
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
}
|
||||
</ui:style>
|
||||
<g:HTMLPanel styleName='{style.difftable}'>
|
||||
<table class='{style.table}'>
|
||||
<tr ui:field='patchSetNavRow' class='{style.patchSetNav}'>
|
||||
<td>
|
||||
<table class='{style.table}'>
|
||||
<tr>
|
||||
<td ui:field='patchSetNavCellA'>
|
||||
<d:PatchSetSelectBox ui:field='patchSetSelectBoxA' />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td ui:field='patchSetNavCellB'>
|
||||
<d:PatchSetSelectBox ui:field='patchSetSelectBoxB' />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ui:field='diffHeaderRow' class='{res.diffTableStyle.diffHeader}'>
|
||||
<td><pre ui:field='diffHeaderText' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td ui:field='cm'/>
|
||||
</tr>
|
||||
</table>
|
||||
<g:FlowPanel ui:field='widgets' visible='false'/>
|
||||
</g:HTMLPanel>
|
||||
</ui:UiBinder>
|
@@ -376,6 +376,14 @@ public class CodeMirror extends JavaScriptObject {
|
||||
return Extras.get(this);
|
||||
}
|
||||
|
||||
public final native LineHandle setGutterMarker(int line, String gutterId, Element value) /*-{
|
||||
return this.setGutterMarker(line, gutterId, value);
|
||||
}-*/;
|
||||
|
||||
public final native LineHandle setGutterMarker(LineHandle line, String gutterId, Element value) /*-{
|
||||
return this.setGutterMarker(line, gutterId, value);
|
||||
}-*/;
|
||||
|
||||
protected CodeMirror() {
|
||||
}
|
||||
|
||||
|
@@ -22,6 +22,7 @@
|
||||
@external .cm-tab;
|
||||
@external .cm-searching;
|
||||
@external .cm-trailingspace;
|
||||
@external .unifiedLineNumber;
|
||||
|
||||
/* Reduce margins around CodeMirror to save space. */
|
||||
.CodeMirror-lines {
|
||||
@@ -61,7 +62,8 @@
|
||||
}
|
||||
|
||||
/* Highlight current line number in the line gutter. */
|
||||
.activeLine .CodeMirror-linenumber {
|
||||
.activeLine .CodeMirror-linenumber,
|
||||
.activeLine .unifiedLineNumber {
|
||||
background-color: #bcf !important;
|
||||
color: #000;
|
||||
}
|
||||
|
Reference in New Issue
Block a user