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.dashboards.DashboardList;
|
||||||
import com.google.gerrit.client.diff.DisplaySide;
|
import com.google.gerrit.client.diff.DisplaySide;
|
||||||
import com.google.gerrit.client.diff.SideBySide;
|
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.documentation.DocScreen;
|
||||||
import com.google.gerrit.client.editor.EditScreen;
|
import com.google.gerrit.client.editor.EditScreen;
|
||||||
import com.google.gerrit.client.groups.GroupApi;
|
import com.google.gerrit.client.groups.GroupApi;
|
||||||
@@ -471,14 +472,16 @@ public class Dispatcher {
|
|||||||
|
|
||||||
if ("".equals(panel) || /* DEPRECATED URL */"cm".equals(panel)) {
|
if ("".equals(panel) || /* DEPRECATED URL */"cm".equals(panel)) {
|
||||||
if (preferUnified()) {
|
if (preferUnified()) {
|
||||||
unified(token, baseId, id);
|
unified(token, baseId, id, side, line);
|
||||||
} else {
|
} else {
|
||||||
codemirror(token, baseId, id, side, line, false);
|
codemirror(token, baseId, id, side, line, false);
|
||||||
}
|
}
|
||||||
} else if ("sidebyside".equals(panel)) {
|
} else if ("sidebyside".equals(panel)) {
|
||||||
codemirror(token, null, id, side, line, false);
|
codemirror(token, null, id, side, line, false);
|
||||||
} else if ("unified".equals(panel)) {
|
} 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)) {
|
} else if ("edit".equals(panel)) {
|
||||||
codemirror(token, null, id, side, line, true);
|
codemirror(token, null, id, side, line, true);
|
||||||
} else {
|
} else {
|
||||||
@@ -490,7 +493,18 @@ public class Dispatcher {
|
|||||||
return DiffView.UNIFIED_DIFF.equals(Gerrit.getUserPreferences().diffView());
|
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 PatchSet.Id baseId,
|
||||||
final Patch.Key id) {
|
final Patch.Key id) {
|
||||||
GWT.runAsync(new AsyncSplit(token) {
|
GWT.runAsync(new AsyncSplit(token) {
|
||||||
|
@@ -15,83 +15,31 @@
|
|||||||
package com.google.gerrit.client.diff;
|
package com.google.gerrit.client.diff;
|
||||||
|
|
||||||
import static com.google.gerrit.client.diff.DisplaySide.A;
|
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.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.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;
|
||||||
import net.codemirror.lib.CodeMirror.LineClassWhere;
|
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.Pos;
|
||||||
import net.codemirror.lib.TextMarker;
|
import net.codemirror.lib.TextMarker;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Colors modified regions for {@link SideBySide}. */
|
/** Colors modified regions for {@link SideBySide} and {@link Unified}. */
|
||||||
class ChunkManager {
|
abstract class ChunkManager {
|
||||||
private static final String DATA_LINES = "_cs2h";
|
static final native void onClick(Element e, JavaScriptObject f)
|
||||||
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)
|
|
||||||
/*-{ e.onclick = f }-*/;
|
/*-{ e.onclick = f }-*/;
|
||||||
|
|
||||||
private final SideBySide host;
|
|
||||||
private final CodeMirror cmA;
|
|
||||||
private final CodeMirror cmB;
|
|
||||||
private final Scrollbar scrollbar;
|
private final Scrollbar scrollbar;
|
||||||
private final LineMapper mapper;
|
private final LineMapper mapper;
|
||||||
|
|
||||||
private List<DiffChunkInfo> chunks;
|
|
||||||
private List<TextMarker> markers;
|
private List<TextMarker> markers;
|
||||||
private List<Runnable> undo;
|
private List<Runnable> undo;
|
||||||
private List<LineWidget> padding;
|
|
||||||
private List<Element> paddingDivs;
|
|
||||||
|
|
||||||
ChunkManager(SideBySide host,
|
ChunkManager(Scrollbar scrollbar) {
|
||||||
CodeMirror cmA,
|
|
||||||
CodeMirror cmB,
|
|
||||||
Scrollbar scrollbar) {
|
|
||||||
this.host = host;
|
|
||||||
this.cmA = cmA;
|
|
||||||
this.cmB = cmB;
|
|
||||||
this.scrollbar = scrollbar;
|
this.scrollbar = scrollbar;
|
||||||
this.mapper = new LineMapper();
|
this.mapper = new LineMapper();
|
||||||
}
|
}
|
||||||
@@ -100,8 +48,14 @@ class ChunkManager {
|
|||||||
return mapper;
|
return mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
DiffChunkInfo getFirst() {
|
Scrollbar getScrollbar() {
|
||||||
return !chunks.isEmpty() ? chunks.get(0) : null;
|
return scrollbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract DiffChunkInfo getFirst();
|
||||||
|
|
||||||
|
List<TextMarker> getMarkers() {
|
||||||
|
return markers;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
@@ -112,133 +66,20 @@ class ChunkManager {
|
|||||||
for (Runnable r : undo) {
|
for (Runnable r : undo) {
|
||||||
r.run();
|
r.run();
|
||||||
}
|
}
|
||||||
for (LineWidget w : padding) {
|
|
||||||
w.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void render(DiffInfo diff) {
|
abstract void render(DiffInfo diff);
|
||||||
chunks = new ArrayList<>();
|
|
||||||
|
void render() {
|
||||||
markers = new ArrayList<>();
|
markers = new ArrayList<>();
|
||||||
undo = 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() {
|
void colorLines(CodeMirror cm, String color, int line, int cnt) {
|
||||||
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) {
|
|
||||||
colorLines(cm, LineClassWhere.WRAP, color, line, line + 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) {
|
final String className, final int start, final int end) {
|
||||||
if (start < end) {
|
if (start < end) {
|
||||||
for (int line = start; line < end; line++) {
|
for (int line = start; line < end; line++) {
|
||||||
@@ -255,78 +96,35 @@ class ChunkManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
abstract Runnable diffChunkNav(final CodeMirror cm, final Direction dir);
|
||||||
* Insert a new padding div below the given line.
|
|
||||||
*
|
void diffChunkNavHelper(List<? extends DiffChunkInfo> chunks, CodeMirror cm, int res, Direction dir) {
|
||||||
* @param cm parent CodeMirror to add extra space into.
|
if (res < 0) {
|
||||||
* @param line line to put the padding below.
|
res = -res - (dir == Direction.PREV ? 1 : 2);
|
||||||
* @param len number of lines to pad. Padding is inserted only if
|
}
|
||||||
* {@code len >= 1}.
|
res = res + (dir == Direction.PREV ? -1 : 1);
|
||||||
*/
|
if (res < 0 || chunks.size() <= res) {
|
||||||
private void addPadding(CodeMirror cm, int line, final int len) {
|
return;
|
||||||
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)));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void addDiffChunk(CodeMirror cmToPad, int lineOnOther,
|
DiffChunkInfo lookUp = chunks.get(res);
|
||||||
int chunkSize, boolean edit) {
|
// If edit, skip the deletion chunk and set focus on the insertion one.
|
||||||
chunks.add(new DiffChunkInfo(host.otherCm(cmToPad).side(),
|
if (lookUp.isEdit() && lookUp.getSide() == A) {
|
||||||
lineOnOther - chunkSize + 1, lineOnOther, edit));
|
res = res + (dir == Direction.PREV ? -1 : 1);
|
||||||
}
|
if (res < 0 || chunks.size() <= res) {
|
||||||
|
return;
|
||||||
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 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,
|
// Chunks are ordered by their starting line. If it's a deletion,
|
||||||
// use its corresponding line on the revision side for comparison.
|
// use its corresponding line on the revision side for comparison.
|
||||||
// In the edit case, put the deletion chunk right before the
|
// In the edit case, put the deletion chunk right before the
|
||||||
@@ -349,23 +147,5 @@ class ChunkManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
DiffChunkInfo getDiffChunk(DisplaySide side, int line) {
|
abstract int getCmLine(int line, DisplaySide side);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -61,7 +61,7 @@ abstract class CommentBox extends Composite {
|
|||||||
fromTo.from(),
|
fromTo.from(),
|
||||||
fromTo.to(),
|
fromTo.to(),
|
||||||
Configuration.create()
|
Configuration.create()
|
||||||
.set("className", DiffTable.style.range()));
|
.set("className", Resources.I.diffTableStyle().range()));
|
||||||
}
|
}
|
||||||
addDomHandler(new MouseOverHandler() {
|
addDomHandler(new MouseOverHandler() {
|
||||||
@Override
|
@Override
|
||||||
@@ -109,7 +109,7 @@ abstract class CommentBox extends Composite {
|
|||||||
fromTo.from(),
|
fromTo.from(),
|
||||||
fromTo.to(),
|
fromTo.to(),
|
||||||
Configuration.create()
|
Configuration.create()
|
||||||
.set("className", DiffTable.style.rangeHighlight()));
|
.set("className", Resources.I.diffTableStyle().rangeHighlight()));
|
||||||
} else if (!highlight && rangeHighlightMarker != null) {
|
} else if (!highlight && rangeHighlightMarker != null) {
|
||||||
rangeHighlightMarker.clear();
|
rangeHighlightMarker.clear();
|
||||||
rangeHighlightMarker = null;
|
rangeHighlightMarker = null;
|
||||||
|
@@ -14,9 +14,6 @@
|
|||||||
|
|
||||||
package com.google.gerrit.client.diff;
|
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.Timer;
|
||||||
import com.google.gwt.user.client.ui.Composite;
|
import com.google.gwt.user.client.ui.Composite;
|
||||||
import com.google.gwt.user.client.ui.FlowPanel;
|
import com.google.gwt.user.client.ui.FlowPanel;
|
||||||
@@ -30,39 +27,28 @@ import net.codemirror.lib.TextMarker.FromTo;
|
|||||||
/**
|
/**
|
||||||
* LineWidget attached to a CodeMirror container.
|
* LineWidget attached to a CodeMirror container.
|
||||||
*
|
*
|
||||||
* When a comment is placed on a line a CommentWidget is created on both sides.
|
* When a comment is placed on a line a CommentWidget is created.
|
||||||
* The group tracks all comment boxes on that same line, and also includes an
|
|
||||||
* empty padding element to keep subsequent lines vertically aligned.
|
|
||||||
*/
|
*/
|
||||||
class CommentGroup extends Composite {
|
abstract class CommentGroup extends Composite {
|
||||||
static void pair(CommentGroup a, CommentGroup b) {
|
|
||||||
a.peer = b;
|
|
||||||
b.peer = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final CommentManager manager;
|
private final CommentManager manager;
|
||||||
private final CodeMirror cm;
|
private final CodeMirror cm;
|
||||||
|
private final DisplaySide side;
|
||||||
private final int line;
|
private final int line;
|
||||||
private final FlowPanel comments;
|
private final FlowPanel comments;
|
||||||
private final Element padding;
|
|
||||||
private LineWidget lineWidget;
|
private LineWidget lineWidget;
|
||||||
private Timer resizeTimer;
|
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.manager = manager;
|
||||||
this.cm = cm;
|
this.cm = cm;
|
||||||
|
this.side = side;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
|
|
||||||
comments = new FlowPanel();
|
comments = new FlowPanel();
|
||||||
comments.setStyleName(Resources.I.style().commentWidgets());
|
comments.setStyleName(Resources.I.style().commentWidgets());
|
||||||
comments.setVisible(false);
|
comments.setVisible(false);
|
||||||
initWidget(new SimplePanel(comments));
|
initWidget(new SimplePanel(comments));
|
||||||
|
|
||||||
padding = DOM.createDiv();
|
|
||||||
padding.setClassName(DiffTable.style.padding());
|
|
||||||
ChunkManager.focusOnClick(padding, cm.side());
|
|
||||||
getElement().appendChild(padding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommentManager getCommentManager() {
|
CommentManager getCommentManager() {
|
||||||
@@ -73,10 +59,6 @@ class CommentGroup extends Composite {
|
|||||||
return cm;
|
return cm;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommentGroup getPeer() {
|
|
||||||
return peer;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getLine() {
|
int getLine() {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
@@ -138,33 +120,19 @@ class CommentGroup extends Composite {
|
|||||||
void remove(DraftBox box) {
|
void remove(DraftBox box) {
|
||||||
comments.remove(box);
|
comments.remove(box);
|
||||||
comments.setVisible(0 < getBoxCount());
|
comments.setVisible(0 < getBoxCount());
|
||||||
|
|
||||||
if (0 < getBoxCount() || 0 < peer.getBoxCount()) {
|
|
||||||
resize();
|
|
||||||
} else {
|
|
||||||
detach();
|
|
||||||
peer.detach();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void detach() {
|
void detach() {
|
||||||
if (lineWidget != null) {
|
if (lineWidget != null) {
|
||||||
lineWidget.clear();
|
lineWidget.clear();
|
||||||
lineWidget = null;
|
lineWidget = null;
|
||||||
updateSelection();
|
updateSelection();
|
||||||
}
|
}
|
||||||
manager.clearLine(cm.side(), line, this);
|
manager.clearLine(side, line, this);
|
||||||
removeFromParent();
|
removeFromParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void attachPair(DiffTable parent) {
|
void attach(DiffTable parent) {
|
||||||
if (lineWidget == null && peer.lineWidget == null) {
|
|
||||||
this.attach(parent);
|
|
||||||
peer.attach(parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void attach(DiffTable parent) {
|
|
||||||
parent.add(this);
|
parent.add(this);
|
||||||
lineWidget = cm.addLineWidget(Math.max(0, line - 1), getElement(),
|
lineWidget = cm.addLineWidget(Math.max(0, line - 1), getElement(),
|
||||||
Configuration.create()
|
Configuration.create()
|
||||||
@@ -174,33 +142,6 @@ class CommentGroup extends Composite {
|
|||||||
.set("insertAt", 0));
|
.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
|
@Override
|
||||||
protected void onUnload() {
|
protected void onUnload() {
|
||||||
super.onUnload();
|
super.onUnload();
|
||||||
@@ -209,13 +150,7 @@ class CommentGroup extends Composite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void resize() {
|
void updateSelection() {
|
||||||
if (lineWidget != null) {
|
|
||||||
adjustPadding(this, peer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSelection() {
|
|
||||||
if (cm.somethingSelected()) {
|
if (cm.somethingSelected()) {
|
||||||
FromTo r = cm.getSelectedRange();
|
FromTo r = cm.getSelectedRange();
|
||||||
if (r.to().line() >= line) {
|
if (r.to().line() >= line) {
|
||||||
@@ -224,27 +159,37 @@ class CommentGroup extends Composite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canComputeHeight() {
|
boolean canComputeHeight() {
|
||||||
return !comments.isVisible() || comments.getOffsetHeight() > 0;
|
return !comments.isVisible() || comments.getOffsetHeight() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int computeHeight() {
|
LineWidget getLineWidget() {
|
||||||
if (comments.isVisible()) {
|
return lineWidget;
|
||||||
// Include margin-bottom: 5px from CSS class.
|
|
||||||
return comments.getOffsetHeight() + 5;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void adjustPadding(CommentGroup a, CommentGroup b) {
|
void setLineWidget(LineWidget widget) {
|
||||||
int apx = a.computeHeight();
|
lineWidget = widget;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
package com.google.gerrit.client.diff;
|
package com.google.gerrit.client.diff;
|
||||||
|
|
||||||
import com.google.gerrit.client.Gerrit;
|
|
||||||
import com.google.gerrit.client.changes.CommentInfo;
|
import com.google.gerrit.client.changes.CommentInfo;
|
||||||
import com.google.gerrit.client.patches.SkippedLine;
|
import com.google.gerrit.client.patches.SkippedLine;
|
||||||
import com.google.gerrit.client.rpc.CallbackGroup;
|
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 com.google.gwt.core.client.JsArray;
|
||||||
|
|
||||||
import net.codemirror.lib.CodeMirror;
|
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.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.SortedMap;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
/** Tracks comment widgets for {@link SideBySide}. */
|
/** Tracks comment widgets for {@link DiffScreen}. */
|
||||||
class CommentManager {
|
abstract class CommentManager {
|
||||||
private final SideBySide host;
|
|
||||||
private final PatchSet.Id base;
|
private final PatchSet.Id base;
|
||||||
private final PatchSet.Id revision;
|
private final PatchSet.Id revision;
|
||||||
private final String path;
|
private final String path;
|
||||||
private final CommentLinkProcessor commentLinkProcessor;
|
private final CommentLinkProcessor commentLinkProcessor;
|
||||||
|
|
||||||
private final Map<String, PublishedBox> published;
|
private final Map<String, PublishedBox> published;
|
||||||
private final SortedMap<Integer, CommentGroup> sideA;
|
|
||||||
private final SortedMap<Integer, CommentGroup> sideB;
|
|
||||||
private final Set<DraftBox> unsavedDrafts;
|
private final Set<DraftBox> unsavedDrafts;
|
||||||
private boolean attached;
|
private boolean attached;
|
||||||
private boolean expandAll;
|
private boolean expandAll;
|
||||||
private boolean open;
|
private boolean open;
|
||||||
|
|
||||||
CommentManager(SideBySide host,
|
CommentManager(
|
||||||
PatchSet.Id base, PatchSet.Id revision,
|
PatchSet.Id base,
|
||||||
|
PatchSet.Id revision,
|
||||||
String path,
|
String path,
|
||||||
CommentLinkProcessor clp,
|
CommentLinkProcessor clp,
|
||||||
boolean open) {
|
boolean open) {
|
||||||
this.host = host;
|
|
||||||
this.base = base;
|
this.base = base;
|
||||||
this.revision = revision;
|
this.revision = revision;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
@@ -67,111 +57,42 @@ class CommentManager {
|
|||||||
this.open = open;
|
this.open = open;
|
||||||
|
|
||||||
published = new HashMap<>();
|
published = new HashMap<>();
|
||||||
sideA = new TreeMap<>();
|
|
||||||
sideB = new TreeMap<>();
|
|
||||||
unsavedDrafts = new HashSet<>();
|
unsavedDrafts = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
SideBySide getSideBySide() {
|
void setAttached(boolean attached) {
|
||||||
return host;
|
this.attached = attached;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setExpandAllComments(boolean b) {
|
boolean isAttached() {
|
||||||
expandAll = b;
|
return attached;
|
||||||
for (CommentGroup g : sideA.values()) {
|
|
||||||
g.setOpenAll(b);
|
|
||||||
}
|
|
||||||
for (CommentGroup g : sideB.values()) {
|
|
||||||
g.setOpenAll(b);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable commentNav(final CodeMirror src, final Direction dir) {
|
void setExpandAll(boolean expandAll) {
|
||||||
return new Runnable() {
|
this.expandAll = expandAll;
|
||||||
@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 render(CommentsCollections in, boolean expandAll) {
|
boolean isExpandAll() {
|
||||||
if (in.publishedBase != null) {
|
return expandAll;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderPublished(DisplaySide forSide, JsArray<CommentInfo> in) {
|
boolean isOpen() {
|
||||||
for (CommentInfo info : Natives.asList(in)) {
|
return open;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)) {
|
for (CommentInfo info : Natives.asList(in)) {
|
||||||
DisplaySide side = displaySide(info, forSide);
|
DisplaySide side = displaySide(info, forSide);
|
||||||
if (side != null) {
|
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) {
|
void setUnsaved(DraftBox box, boolean isUnsaved) {
|
||||||
if (isUnsaved) {
|
if (isUnsaved) {
|
||||||
unsavedDrafts.add(box);
|
unsavedDrafts.add(box);
|
||||||
@@ -385,52 +115,42 @@ class CommentManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommentGroup group(DisplaySide side, int line) {
|
Side getStoredSideFromDisplaySide(DisplaySide side) {
|
||||||
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) {
|
|
||||||
return side == DisplaySide.A && base == null ? Side.PARENT : Side.REVISION;
|
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;
|
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();
|
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) {
|
private static void append(StringBuilder s, JsArrayString lines) {
|
||||||
for (int i = 0; i < lines.length(); i++) {
|
for (int i = 0; i < lines.length(); i++) {
|
||||||
s.append(lines.get(i)).append('\n');
|
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.client.info.ChangeInfo.RevisionInfo;
|
||||||
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
|
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
|
||||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
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.JsArray;
|
||||||
import com.google.gwt.core.client.JsArrayString;
|
import com.google.gwt.core.client.JsArrayString;
|
||||||
import com.google.gwt.dom.client.Element;
|
import com.google.gwt.dom.client.Element;
|
||||||
import com.google.gwt.dom.client.Style.Unit;
|
import com.google.gwt.dom.client.Style.Unit;
|
||||||
import com.google.gwt.resources.client.CssResource;
|
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.UiField;
|
||||||
import com.google.gwt.user.client.ui.Composite;
|
import com.google.gwt.user.client.ui.Composite;
|
||||||
import com.google.gwt.user.client.ui.FlowPanel;
|
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.UIObject;
|
||||||
import com.google.gwt.user.client.ui.Widget;
|
import com.google.gwt.user.client.ui.Widget;
|
||||||
|
|
||||||
import net.codemirror.lib.CodeMirror;
|
import net.codemirror.lib.CodeMirror;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A table with one row and two columns to hold the two CodeMirrors displaying
|
* Base class for SideBySideTable2 and UnifiedTable2
|
||||||
* the files to be diffed.
|
|
||||||
*/
|
*/
|
||||||
class DiffTable extends Composite {
|
abstract class DiffTable extends Composite {
|
||||||
interface Binder extends UiBinder<HTMLPanel, DiffTable> {}
|
static {
|
||||||
private static final Binder uiBinder = GWT.create(Binder.class);
|
Resources.I.diffTableStyle().ensureInjected();
|
||||||
|
}
|
||||||
|
|
||||||
interface DiffTableStyle extends CssResource {
|
interface Style extends CssResource {
|
||||||
String fullscreen();
|
String fullscreen();
|
||||||
String intralineBg();
|
|
||||||
String dark();
|
String dark();
|
||||||
String diff();
|
|
||||||
String noIntraline();
|
String noIntraline();
|
||||||
String range();
|
String range();
|
||||||
String rangeHighlight();
|
String rangeHighlight();
|
||||||
String showLineNumbers();
|
String diffHeader();
|
||||||
String hideA();
|
|
||||||
String hideB();
|
|
||||||
String padding();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiField Element cmA;
|
|
||||||
@UiField Element cmB;
|
|
||||||
Scrollbar scrollbar;
|
|
||||||
@UiField Element patchSetNavRow;
|
@UiField Element patchSetNavRow;
|
||||||
@UiField Element patchSetNavCellA;
|
@UiField Element patchSetNavCellA;
|
||||||
@UiField Element patchSetNavCellB;
|
@UiField Element patchSetNavCellB;
|
||||||
@UiField Element diffHeaderRow;
|
@UiField Element diffHeaderRow;
|
||||||
@UiField Element diffHeaderText;
|
@UiField Element diffHeaderText;
|
||||||
@UiField FlowPanel widgets;
|
@UiField FlowPanel widgets;
|
||||||
@UiField static DiffTableStyle style;
|
|
||||||
|
|
||||||
@UiField(provided = true)
|
@UiField(provided = true)
|
||||||
PatchSetSelectBox patchSetSelectBoxA;
|
PatchSetSelectBox patchSetSelectBoxA;
|
||||||
@@ -73,65 +61,31 @@ class DiffTable extends Composite {
|
|||||||
@UiField(provided = true)
|
@UiField(provided = true)
|
||||||
PatchSetSelectBox patchSetSelectBoxB;
|
PatchSetSelectBox patchSetSelectBoxB;
|
||||||
|
|
||||||
private SideBySide parent;
|
|
||||||
private boolean header;
|
private boolean header;
|
||||||
private boolean visibleA;
|
|
||||||
private ChangeType changeType;
|
private ChangeType changeType;
|
||||||
|
Scrollbar scrollbar;
|
||||||
|
|
||||||
DiffTable(SideBySide parent, PatchSet.Id base, PatchSet.Id revision,
|
DiffTable(DiffScreen parent, PatchSet.Id base, PatchSet.Id revision, String path) {
|
||||||
String path) {
|
|
||||||
patchSetSelectBoxA = new PatchSetSelectBox(
|
patchSetSelectBoxA = new PatchSetSelectBox(
|
||||||
parent, DisplaySide.A, revision.getParentKey(), base, path);
|
parent, DisplaySide.A, revision.getParentKey(), base, path);
|
||||||
patchSetSelectBoxB = new PatchSetSelectBox(
|
patchSetSelectBoxB = new PatchSetSelectBox(
|
||||||
parent, DisplaySide.B, revision.getParentKey(), revision, path);
|
parent, DisplaySide.B, revision.getParentKey(), revision, path);
|
||||||
PatchSetSelectBox.link(patchSetSelectBoxA, patchSetSelectBoxB);
|
PatchSetSelectBox.link(patchSetSelectBoxA, patchSetSelectBoxB);
|
||||||
|
|
||||||
initWidget(uiBinder.createAndBindUi(this));
|
|
||||||
this.scrollbar = new Scrollbar(this);
|
this.scrollbar = new Scrollbar(this);
|
||||||
this.parent = parent;
|
|
||||||
this.visibleA = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isVisibleA() {
|
abstract 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setHeaderVisible(boolean show) {
|
void setHeaderVisible(boolean show) {
|
||||||
|
DiffScreen parent = getDiffScreen();
|
||||||
if (show != UIObject.isVisible(patchSetNavRow)) {
|
if (show != UIObject.isVisible(patchSetNavRow)) {
|
||||||
UIObject.setVisible(patchSetNavRow, show);
|
UIObject.setVisible(patchSetNavRow, show);
|
||||||
UIObject.setVisible(diffHeaderRow, show && header);
|
UIObject.setVisible(diffHeaderRow, show && header);
|
||||||
if (show) {
|
if (show) {
|
||||||
parent.header.removeStyleName(style.fullscreen());
|
parent.header.removeStyleName(Resources.I.diffTableStyle().fullscreen());
|
||||||
} else {
|
} else {
|
||||||
parent.header.addStyleName(style.fullscreen());
|
parent.header.addStyleName(Resources.I.diffTableStyle().fullscreen());
|
||||||
}
|
}
|
||||||
parent.resizeCodeMirror();
|
parent.resizeCodeMirror();
|
||||||
}
|
}
|
||||||
@@ -182,17 +136,11 @@ class DiffTable extends Composite {
|
|||||||
setHideEmptyPane(prefs.hideEmptyPane());
|
setHideEmptyPane(prefs.hideEmptyPane());
|
||||||
}
|
}
|
||||||
|
|
||||||
void setHideEmptyPane(boolean hide) {
|
abstract void setHideEmptyPane(boolean hide);
|
||||||
if (changeType == ChangeType.ADDED) {
|
|
||||||
setVisibleA(!hide);
|
|
||||||
} else if (changeType == ChangeType.DELETED) {
|
|
||||||
setVisibleB(!hide);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void refresh() {
|
void refresh() {
|
||||||
if (header) {
|
if (header) {
|
||||||
CodeMirror cm = parent.getCmFromSide(DisplaySide.A);
|
CodeMirror cm = getDiffScreen().getCmFromSide(DisplaySide.A);
|
||||||
diffHeaderText.getStyle().setMarginLeft(
|
diffHeaderText.getStyle().setMarginLeft(
|
||||||
cm.getGutterElement().getOffsetWidth(),
|
cm.getGutterElement().getOffsetWidth(),
|
||||||
Unit.PX);
|
Unit.PX);
|
||||||
@@ -202,4 +150,6 @@ class DiffTable extends Composite {
|
|||||||
void add(Widget widget) {
|
void add(Widget widget) {
|
||||||
widgets.add(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.ReviewInfo;
|
||||||
import com.google.gerrit.client.changes.Util;
|
import com.google.gerrit.client.changes.Util;
|
||||||
import com.google.gerrit.client.diff.DiffInfo.Region;
|
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;
|
||||||
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
|
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
|
||||||
import com.google.gerrit.client.info.FileInfo;
|
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 base;
|
||||||
private final PatchSet.Id patchSetId;
|
private final PatchSet.Id patchSetId;
|
||||||
private final String path;
|
private final String path;
|
||||||
|
private final DiffScreenType diffScreenType;
|
||||||
private boolean hasPrev;
|
private boolean hasPrev;
|
||||||
private boolean hasNext;
|
private boolean hasNext;
|
||||||
private String nextPath;
|
private String nextPath;
|
||||||
@@ -96,12 +98,13 @@ public class Header extends Composite {
|
|||||||
private ReviewedState reviewedState;
|
private ReviewedState reviewedState;
|
||||||
|
|
||||||
Header(KeyCommandSet keys, PatchSet.Id base, PatchSet.Id patchSetId,
|
Header(KeyCommandSet keys, PatchSet.Id base, PatchSet.Id patchSetId,
|
||||||
String path) {
|
String path, DiffScreenType diffSreenType) {
|
||||||
initWidget(uiBinder.createAndBindUi(this));
|
initWidget(uiBinder.createAndBindUi(this));
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
this.base = base;
|
this.base = base;
|
||||||
this.patchSetId = patchSetId;
|
this.patchSetId = patchSetId;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
|
this.diffScreenType = diffSreenType;
|
||||||
|
|
||||||
if (!Gerrit.isSignedIn()) {
|
if (!Gerrit.isSignedIn()) {
|
||||||
reviewed.getElement().getStyle().setVisibility(Visibility.HIDDEN);
|
reviewed.getElement().getStyle().setVisibility(Visibility.HIDDEN);
|
||||||
@@ -267,9 +270,9 @@ public class Header extends Composite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String url(FileInfo info) {
|
private String url(FileInfo info) {
|
||||||
return info.binary()
|
return diffScreenType == DiffScreenType.UNIFIED || info.binary()
|
||||||
? Dispatcher.toUnified(base, patchSetId, info.path())
|
? Dispatcher.toUnified(base, patchSetId, info.path())
|
||||||
: Dispatcher.toSideBySide(base, patchSetId, info.path());
|
: Dispatcher.toSideBySide(base, patchSetId, info.path());
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyCommand setupNav(InlineHyperlink link, char key, String help, FileInfo info) {
|
private KeyCommand setupNav(InlineHyperlink link, char key, String help, FileInfo info) {
|
||||||
|
@@ -43,7 +43,7 @@ class InsertCommentBubble extends Composite {
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(ClickEvent event) {
|
public void onClick(ClickEvent event) {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
commentManager.insertNewDraft(cm).run();
|
commentManager.newDraftCallback(cm).run();
|
||||||
}
|
}
|
||||||
}, ClickEvent.getType());
|
}, ClickEvent.getType());
|
||||||
}
|
}
|
||||||
|
@@ -54,7 +54,7 @@ class PatchSetSelectBox extends Composite {
|
|||||||
@UiField HTMLPanel linkPanel;
|
@UiField HTMLPanel linkPanel;
|
||||||
@UiField BoxStyle style;
|
@UiField BoxStyle style;
|
||||||
|
|
||||||
private SideBySide parent;
|
private DiffScreen parent;
|
||||||
private DisplaySide side;
|
private DisplaySide side;
|
||||||
private boolean sideA;
|
private boolean sideA;
|
||||||
private String path;
|
private String path;
|
||||||
@@ -63,7 +63,7 @@ class PatchSetSelectBox extends Composite {
|
|||||||
private PatchSet.Id idActive;
|
private PatchSet.Id idActive;
|
||||||
private PatchSetSelectBox other;
|
private PatchSetSelectBox other;
|
||||||
|
|
||||||
PatchSetSelectBox(SideBySide parent,
|
PatchSetSelectBox(DiffScreen parent,
|
||||||
DisplaySide side,
|
DisplaySide side,
|
||||||
Change.Id changeId,
|
Change.Id changeId,
|
||||||
PatchSet.Id revision,
|
PatchSet.Id revision,
|
||||||
@@ -143,10 +143,12 @@ class PatchSetSelectBox extends Composite {
|
|||||||
if (sideA) {
|
if (sideA) {
|
||||||
assert other.idActive != null;
|
assert other.idActive != null;
|
||||||
}
|
}
|
||||||
return new InlineHyperlink(label, Dispatcher.toSideBySide(
|
PatchSet.Id diffBase = sideA ? id : other.idActive;
|
||||||
sideA ? id : other.idActive,
|
PatchSet.Id revision = sideA ? other.idActive : id;
|
||||||
sideA ? other.idActive : id,
|
|
||||||
path));
|
return new InlineHyperlink(label, parent instanceof SideBySide
|
||||||
|
? Dispatcher.toSideBySide(diffBase, revision, path)
|
||||||
|
: Dispatcher.toUnified(diffBase, revision, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Anchor createDownloadLink() {
|
private Anchor createDownloadLink() {
|
||||||
|
@@ -22,13 +22,13 @@ import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
|
|||||||
import com.google.gwt.user.client.ui.Widget;
|
import com.google.gwt.user.client.ui.Widget;
|
||||||
|
|
||||||
class PreferencesAction {
|
class PreferencesAction {
|
||||||
private final SideBySide view;
|
private final DiffScreen view;
|
||||||
private final DiffPreferences prefs;
|
private final DiffPreferences prefs;
|
||||||
private PopupPanel popup;
|
private PopupPanel popup;
|
||||||
private PreferencesBox current;
|
private PreferencesBox current;
|
||||||
private Widget partner;
|
private Widget partner;
|
||||||
|
|
||||||
PreferencesAction(SideBySide view, DiffPreferences prefs) {
|
PreferencesAction(DiffScreen view, DiffPreferences prefs) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.prefs = prefs;
|
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.ToggleButton;
|
||||||
import com.google.gwt.user.client.ui.UIObject;
|
import com.google.gwt.user.client.ui.UIObject;
|
||||||
|
|
||||||
|
import net.codemirror.lib.CodeMirror;
|
||||||
import net.codemirror.mode.ModeInfo;
|
import net.codemirror.mode.ModeInfo;
|
||||||
import net.codemirror.mode.ModeInjector;
|
import net.codemirror.mode.ModeInjector;
|
||||||
import net.codemirror.theme.ThemeLoader;
|
import net.codemirror.theme.ThemeLoader;
|
||||||
@@ -73,7 +74,7 @@ public class PreferencesBox extends Composite {
|
|||||||
String dialog();
|
String dialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final SideBySide view;
|
private final DiffScreen view;
|
||||||
private DiffPreferences prefs;
|
private DiffPreferences prefs;
|
||||||
private int contextLastValue;
|
private int contextLastValue;
|
||||||
private Timer updateContextTimer;
|
private Timer updateContextTimer;
|
||||||
@@ -107,7 +108,7 @@ public class PreferencesBox extends Composite {
|
|||||||
@UiField Button apply;
|
@UiField Button apply;
|
||||||
@UiField Button save;
|
@UiField Button save;
|
||||||
|
|
||||||
public PreferencesBox(SideBySide view) {
|
public PreferencesBox(DiffScreen view) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
|
|
||||||
initWidget(uiBinder.createAndBindUi(this));
|
initWidget(uiBinder.createAndBindUi(this));
|
||||||
@@ -181,9 +182,9 @@ public class PreferencesBox extends Composite {
|
|||||||
lineNumbers.setValue(prefs.showLineNumbers());
|
lineNumbers.setValue(prefs.showLineNumbers());
|
||||||
emptyPane.setValue(!prefs.hideEmptyPane());
|
emptyPane.setValue(!prefs.hideEmptyPane());
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
leftSide.setValue(view.diffTable.isVisibleA());
|
leftSide.setValue(view.getDiffTable().isVisibleA());
|
||||||
leftSide.setEnabled(!(prefs.hideEmptyPane()
|
leftSide.setEnabled(!(prefs.hideEmptyPane()
|
||||||
&& view.diffTable.getChangeType() == ChangeType.ADDED));
|
&& view.getDiffTable().getChangeType() == ChangeType.ADDED));
|
||||||
} else {
|
} else {
|
||||||
UIObject.setVisible(leftSideLabel, false);
|
UIObject.setVisible(leftSideLabel, false);
|
||||||
leftSide.setVisible(false);
|
leftSide.setVisible(false);
|
||||||
@@ -315,8 +316,9 @@ public class PreferencesBox extends Composite {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
int v = prefs.tabSize();
|
int v = prefs.tabSize();
|
||||||
view.getCmFromSide(DisplaySide.A).setOption("tabSize", v);
|
for (CodeMirror cm : view.getCms()) {
|
||||||
view.getCmFromSide(DisplaySide.B).setOption("tabSize", v);
|
cm.setOption("tabSize", v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -378,21 +380,23 @@ public class PreferencesBox extends Composite {
|
|||||||
|
|
||||||
@UiHandler("leftSide")
|
@UiHandler("leftSide")
|
||||||
void onLeftSide(ValueChangeEvent<Boolean> e) {
|
void onLeftSide(ValueChangeEvent<Boolean> e) {
|
||||||
view.diffTable.setVisibleA(e.getValue());
|
if (view.getDiffTable() instanceof SideBySideTable) {
|
||||||
|
((SideBySideTable) view.getDiffTable()).setVisibleA(e.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiHandler("emptyPane")
|
@UiHandler("emptyPane")
|
||||||
void onHideEmptyPane(ValueChangeEvent<Boolean> e) {
|
void onHideEmptyPane(ValueChangeEvent<Boolean> e) {
|
||||||
prefs.hideEmptyPane(!e.getValue());
|
prefs.hideEmptyPane(!e.getValue());
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
view.diffTable.setHideEmptyPane(prefs.hideEmptyPane());
|
view.getDiffTable().setHideEmptyPane(prefs.hideEmptyPane());
|
||||||
if (prefs.hideEmptyPane()) {
|
if (prefs.hideEmptyPane()) {
|
||||||
if (view.diffTable.getChangeType() == ChangeType.ADDED) {
|
if (view.getDiffTable().getChangeType() == ChangeType.ADDED) {
|
||||||
leftSide.setValue(false);
|
leftSide.setValue(false);
|
||||||
leftSide.setEnabled(false);
|
leftSide.setEnabled(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
leftSide.setValue(view.diffTable.isVisibleA());
|
leftSide.setValue(view.getDiffTable().isVisibleA());
|
||||||
leftSide.setEnabled(true);
|
leftSide.setEnabled(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,8 +472,9 @@ public class PreferencesBox extends Composite {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
boolean s = prefs.showWhitespaceErrors();
|
boolean s = prefs.showWhitespaceErrors();
|
||||||
view.getCmFromSide(DisplaySide.A).setOption("showTrailingSpace", s);
|
for (CodeMirror cm : view.getCms()) {
|
||||||
view.getCmFromSide(DisplaySide.B).setOption("showTrailingSpace", s);
|
cm.setOption("showTrailingSpace", s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -158,7 +158,7 @@ class PublishedBox extends CommentBox {
|
|||||||
|
|
||||||
void doReply() {
|
void doReply() {
|
||||||
if (!Gerrit.isSignedIn()) {
|
if (!Gerrit.isSignedIn()) {
|
||||||
Gerrit.doSignIn(getCommentManager().getSideBySide().getToken());
|
Gerrit.doSignIn(getCommentManager().getDiffScreen().getToken());
|
||||||
} else if (replyBox == null) {
|
} else if (replyBox == null) {
|
||||||
addReplyBox(false);
|
addReplyBox(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -176,7 +176,7 @@ class PublishedBox extends CommentBox {
|
|||||||
void onQuote(ClickEvent e) {
|
void onQuote(ClickEvent e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!Gerrit.isSignedIn()) {
|
if (!Gerrit.isSignedIn()) {
|
||||||
Gerrit.doSignIn(getCommentManager().getSideBySide().getToken());
|
Gerrit.doSignIn(getCommentManager().getDiffScreen().getToken());
|
||||||
}
|
}
|
||||||
addReplyBox(true);
|
addReplyBox(true);
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ class PublishedBox extends CommentBox {
|
|||||||
void onReplyDone(ClickEvent e) {
|
void onReplyDone(ClickEvent e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!Gerrit.isSignedIn()) {
|
if (!Gerrit.isSignedIn()) {
|
||||||
Gerrit.doSignIn(getCommentManager().getSideBySide().getToken());
|
Gerrit.doSignIn(getCommentManager().getDiffScreen().getToken());
|
||||||
} else if (replyBox == null) {
|
} else if (replyBox == null) {
|
||||||
done.setEnabled(false);
|
done.setEnabled(false);
|
||||||
CommentInfo input = CommentInfo.createReply(comment);
|
CommentInfo input = CommentInfo.createReply(comment);
|
||||||
|
@@ -24,6 +24,7 @@ interface Resources extends ClientBundle {
|
|||||||
|
|
||||||
@Source("CommentBox.css") CommentBox.Style style();
|
@Source("CommentBox.css") CommentBox.Style style();
|
||||||
@Source("Scrollbar.css") Scrollbar.Style scrollbarStyle();
|
@Source("Scrollbar.css") Scrollbar.Style scrollbarStyle();
|
||||||
|
@Source("DiffTable.css") DiffTable.Style diffTableStyle();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tango icon library (public domain):
|
* tango icon library (public domain):
|
||||||
|
@@ -20,7 +20,7 @@ import net.codemirror.lib.CodeMirror;
|
|||||||
import net.codemirror.lib.ScrollInfo;
|
import net.codemirror.lib.ScrollInfo;
|
||||||
|
|
||||||
class ScrollSynchronizer {
|
class ScrollSynchronizer {
|
||||||
private DiffTable diffTable;
|
private SideBySideTable diffTable;
|
||||||
private LineMapper mapper;
|
private LineMapper mapper;
|
||||||
private ScrollCallback active;
|
private ScrollCallback active;
|
||||||
private ScrollCallback callbackA;
|
private ScrollCallback callbackA;
|
||||||
@@ -28,7 +28,7 @@ class ScrollSynchronizer {
|
|||||||
private CodeMirror cmB;
|
private CodeMirror cmB;
|
||||||
private boolean autoHideDiffTableHeader;
|
private boolean autoHideDiffTableHeader;
|
||||||
|
|
||||||
ScrollSynchronizer(DiffTable diffTable,
|
ScrollSynchronizer(SideBySideTable diffTable,
|
||||||
CodeMirror cmA, CodeMirror cmB,
|
CodeMirror cmA, CodeMirror cmB,
|
||||||
LineMapper mapper) {
|
LineMapper mapper) {
|
||||||
this.diffTable = diffTable;
|
this.diffTable = diffTable;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,6 @@ limitations under the License.
|
|||||||
</ui:style>
|
</ui:style>
|
||||||
<g:FlowPanel styleName='{style.sbs}'>
|
<g:FlowPanel styleName='{style.sbs}'>
|
||||||
<d:Header ui:field='header'/>
|
<d:Header ui:field='header'/>
|
||||||
<d:DiffTable ui:field='diffTable'/>
|
<d:SideBySideTable ui:field='diffTable'/>
|
||||||
</g:FlowPanel>
|
</g:FlowPanel>
|
||||||
</ui:UiBinder>
|
</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'
|
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
||||||
xmlns:g='urn:import:com.google.gwt.user.client.ui'
|
xmlns:g='urn:import:com.google.gwt.user.client.ui'
|
||||||
xmlns:d='urn:import:com.google.gerrit.client.diff'>
|
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, .CodeMirror-selectedtext;
|
||||||
@external .CodeMirror-linenumber;
|
@external .CodeMirror-linenumber;
|
||||||
@external .CodeMirror-overlayscroll-vertical, .CodeMirror-scroll;
|
@external .CodeMirror-overlayscroll-vertical, .CodeMirror-scroll;
|
||||||
@external .CodeMirror-dialog-bottom;
|
@external .CodeMirror-dialog-bottom;
|
||||||
@external .CodeMirror-cursor;
|
@external .CodeMirror-cursor;
|
||||||
|
|
||||||
.fullscreen {
|
@external .dark, .noIntraline;
|
||||||
background-color: #f7f7f7;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.difftable .patchSetNav,
|
.difftable .patchSetNav,
|
||||||
.difftable .CodeMirror {
|
.difftable .CodeMirror {
|
||||||
@@ -98,16 +96,7 @@ limitations under the License.
|
|||||||
background-color: #f7f7f7;
|
background-color: #f7f7f7;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.fileCommentCell {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range {
|
|
||||||
background-color: #ffd500 !important;
|
|
||||||
}
|
|
||||||
.rangeHighlight {
|
|
||||||
background-color: #ffff00 !important;
|
|
||||||
}
|
|
||||||
.difftable .CodeMirror-selectedtext {
|
.difftable .CodeMirror-selectedtext {
|
||||||
background-color: inherit !important;
|
background-color: inherit !important;
|
||||||
}
|
}
|
||||||
@@ -132,15 +121,6 @@ limitations under the License.
|
|||||||
margin-left: 21px;
|
margin-left: 21px;
|
||||||
border-left: 2px solid #d64040;
|
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>
|
</ui:style>
|
||||||
<g:HTMLPanel styleName='{style.difftable}'>
|
<g:HTMLPanel styleName='{style.difftable}'>
|
||||||
<table class='{style.table}'>
|
<table class='{style.table}'>
|
||||||
@@ -152,7 +132,7 @@ limitations under the License.
|
|||||||
<d:PatchSetSelectBox ui:field='patchSetSelectBoxB' />
|
<d:PatchSetSelectBox ui:field='patchSetSelectBoxB' />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
<td colspan='2'><pre ui:field='diffHeaderText' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
@@ -14,189 +14,9 @@
|
|||||||
|
|
||||||
package com.google.gerrit.client.diff;
|
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.Composite;
|
||||||
import com.google.gwt.user.client.ui.HTMLPanel;
|
|
||||||
|
|
||||||
import net.codemirror.lib.CodeMirror;
|
abstract class SkipBar extends Composite {
|
||||||
import net.codemirror.lib.Configuration;
|
abstract void expandSideAll();
|
||||||
import net.codemirror.lib.LineWidget;
|
abstract void expandBefore(int cnt);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -19,34 +19,34 @@ import com.google.gerrit.client.patches.SkippedLine;
|
|||||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
||||||
import com.google.gwt.core.client.JsArray;
|
import com.google.gwt.core.client.JsArray;
|
||||||
|
|
||||||
import net.codemirror.lib.CodeMirror;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/** Collapses common regions with {@link SkipBar} for {@link SideBySide}. */
|
/** Collapses common regions with {@link SideBySideSkipBar} for {@link SideBySide}
|
||||||
class SkipManager {
|
* and {@link Unified}. */
|
||||||
private final SideBySide host;
|
abstract class SkipManager {
|
||||||
private final CommentManager commentManager;
|
|
||||||
private Set<SkipBar> skipBars;
|
private Set<SkipBar> skipBars;
|
||||||
private SkipBar line0;
|
private SkipBar line0;
|
||||||
|
private CommentManager commentManager;
|
||||||
|
private int lineA;
|
||||||
|
private int lineB;
|
||||||
|
|
||||||
SkipManager(SideBySide host, CommentManager commentManager) {
|
SkipManager(CommentManager commentManager) {
|
||||||
this.host = host;
|
|
||||||
this.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) {
|
if (context == DiffPreferencesInfo.WHOLE_FILE_CONTEXT) {
|
||||||
return;
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lineA = 0;
|
||||||
|
lineB = 0;
|
||||||
JsArray<Region> regions = diff.content();
|
JsArray<Region> regions = diff.content();
|
||||||
List<SkippedLine> skips = new ArrayList<>();
|
List<SkippedLine> skips = new ArrayList<>();
|
||||||
int lineA = 0;
|
|
||||||
int lineB = 0;
|
|
||||||
for (int i = 0; i < regions.length(); i++) {
|
for (int i = 0; i < regions.length(); i++) {
|
||||||
Region current = regions.get(i);
|
Region current = regions.get(i);
|
||||||
if (current.ab() != null || current.common() || current.skip() > 0) {
|
if (current.ab() != null || current.common() || current.skip() > 0) {
|
||||||
@@ -69,31 +69,7 @@ class SkipManager {
|
|||||||
lineB += current.b() != null ? current.b().length() : 0;
|
lineB += current.b() != null ? current.b().length() : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
skips = commentManager.splitSkips(context, skips);
|
return 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ensureFirstLineIsVisible() {
|
void ensureFirstLineIsVisible() {
|
||||||
@@ -113,24 +89,27 @@ class SkipManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(SkipBar a, SkipBar b) {
|
SkipBar getLine0() {
|
||||||
skipBars.remove(a);
|
return line0;
|
||||||
skipBars.remove(b);
|
|
||||||
if (line0 == a || line0 == b) {
|
|
||||||
line0 = null;
|
|
||||||
}
|
|
||||||
if (skipBars.isEmpty()) {
|
|
||||||
skipBars = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkipBar newSkipBar(CodeMirror cm, DisplaySide side, SkippedLine skip) {
|
int getLineA() {
|
||||||
int start = side == DisplaySide.A ? skip.getStartA() : skip.getStartB();
|
return lineA;
|
||||||
int end = start + skip.getSize() - 1;
|
}
|
||||||
|
|
||||||
SkipBar bar = new SkipBar(this, cm);
|
int getLineB() {
|
||||||
host.diffTable.add(bar);
|
return lineB;
|
||||||
bar.collapse(start, end, true);
|
}
|
||||||
return bar;
|
|
||||||
|
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'
|
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
|
||||||
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
|
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 {
|
.skipBar {
|
||||||
background-color: #def;
|
background-color: #def;
|
||||||
height: 1.3em;
|
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);
|
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() {
|
protected CodeMirror() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@
|
|||||||
@external .cm-tab;
|
@external .cm-tab;
|
||||||
@external .cm-searching;
|
@external .cm-searching;
|
||||||
@external .cm-trailingspace;
|
@external .cm-trailingspace;
|
||||||
|
@external .unifiedLineNumber;
|
||||||
|
|
||||||
/* Reduce margins around CodeMirror to save space. */
|
/* Reduce margins around CodeMirror to save space. */
|
||||||
.CodeMirror-lines {
|
.CodeMirror-lines {
|
||||||
@@ -61,7 +62,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Highlight current line number in the line gutter. */
|
/* Highlight current line number in the line gutter. */
|
||||||
.activeLine .CodeMirror-linenumber {
|
.activeLine .CodeMirror-linenumber,
|
||||||
|
.activeLine .unifiedLineNumber {
|
||||||
background-color: #bcf !important;
|
background-color: #bcf !important;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user