New unified diff based on CodeMirror
Migrate unified diff screen from prettify to CodeMirror, reusing as much as possible from the side by side diff view. This now builds and mostly works. TODO: 1. Maybe implement proper behavior for range based comments. - Currently, pressing 'c' with text selected does not open a comment. 2. Support hiding line numbers in Unified. 3. Factor out more common code, minimize duplication. - UnifiedCommentManager and SideBySideCommentManager. - UnifiedSkipBar and SideBySideSkipBar. These will be addressed in follow-up commits. Change-Id: I91935613510879436a911ff1dcc191c729f0f06d
This commit is contained in:
committed by
David Pursehouse
parent
cacac00870
commit
f156a4d76a
@@ -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