blob: 6379e231214fbcd76294c942c02b2298803ac57e [file] [log] [blame]
// Copyright (C) 2008 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.patches;
import static com.google.gerrit.client.patches.PatchLine.Type.CONTEXT;
import static com.google.gerrit.client.patches.PatchLine.Type.DELETE;
import static com.google.gerrit.client.patches.PatchLine.Type.INSERT;
import static com.google.gerrit.client.patches.PatchLine.Type.REPLACE;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScript.FileMode;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseHtmlFile;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import com.google.gwtorm.client.KeyUtil;
import org.eclipse.jgit.diff.Edit;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class SideBySideTable extends AbstractPatchContentTable {
private static final int COL_A = 2;
private static final int COL_B = 4;
private static final int NUM_ROWS_TO_EXPAND = 10;
private SparseHtmlFile a;
private SparseHtmlFile b;
@Override
protected void onCellDoubleClick(final int row, int column) {
if (column > 0 && getRowItem(row) instanceof PatchLine) {
final PatchLine line = (PatchLine) getRowItem(row);
final short file = (short) ((column - 1) / 2);
if (column < (1 + file * 2 + 1)) {
column++;
}
switch (file) {
case 0:
createCommentEditor(row + 1, column, line.getLineA(), file);
break;
case 1:
createCommentEditor(row + 1, column, line.getLineB(), file);
break;
}
}
}
@Override
protected void onCellSingleClick(int row, int column) {
if (column == 1 || column == 3) {
onCellDoubleClick(row, column);
}
}
@Override
protected void onInsertComment(final PatchLine line) {
final int row = getCurrentRow();
createCommentEditor(row + 1, 4, line.getLineB(), (short) 1);
}
@Override
protected void render(final PatchScript script) {
a = getSparseHtmlFileA(script);
b = getSparseHtmlFileB(script);
final ArrayList<Object> lines = new ArrayList<Object>();
final SafeHtmlBuilder nc = new SafeHtmlBuilder();
final boolean intraline =
script.getDiffPrefs().isIntralineDifference()
&& script.hasIntralineDifference();
appendHeader(script, nc);
lines.add(null);
if(script.getFileModeA()!=FileMode.FILE||script.getFileModeB()!=FileMode.FILE){
openLine(nc);
appendModeLine(nc, script.getFileModeA());
appendModeLine(nc, script.getFileModeB());
closeLine(nc);
lines.add(null);
}
int lastA = 0;
int lastB = 0;
final boolean ignoreWS = script.isIgnoreWhitespace();
for (final EditList.Hunk hunk : script.getHunks()) {
if (!hunk.isStartOfFile()) {
appendSkipLine(nc, hunk.getCurB() - lastB);
lines.add(new SkippedLine(lastA, lastB, hunk.getCurB() - lastB));
}
while (hunk.next()) {
if (hunk.isContextLine()) {
openLine(nc);
final SafeHtml ctx = a.getSafeHtmlLine(hunk.getCurA());
appendLineText(nc, hunk.getCurA(), CONTEXT, ctx, false, false);
if (ignoreWS && b.contains(hunk.getCurB())) {
appendLineText(nc, hunk.getCurB(), CONTEXT, b, hunk.getCurB(),
false);
} else {
appendLineText(nc, hunk.getCurB(), CONTEXT, ctx, false, false);
}
closeLine(nc);
hunk.incBoth();
lines.add(new PatchLine(CONTEXT, hunk.getCurA(), hunk.getCurB()));
} else if (hunk.isModifiedLine()) {
final boolean del = hunk.isDeletedA();
final boolean ins = hunk.isInsertedB();
final boolean full =
intraline && hunk.getCurEdit().getType() != Edit.Type.REPLACE;
openLine(nc);
if (del) {
appendLineText(nc, hunk.getCurA(), DELETE, a, hunk.getCurA(), full);
hunk.incA();
} else if (hunk.getCurEdit().getType() == Edit.Type.REPLACE) {
appendLineNone(nc, DELETE);
} else {
appendLineNone(nc, CONTEXT);
}
if (ins) {
appendLineText(nc, hunk.getCurB(), INSERT, b, hunk.getCurB(), full);
hunk.incB();
} else if (hunk.getCurEdit().getType() == Edit.Type.REPLACE) {
appendLineNone(nc, INSERT);
} else {
appendLineNone(nc, CONTEXT);
}
closeLine(nc);
if (del && ins) {
lines.add(new PatchLine(REPLACE, hunk.getCurA(), hunk.getCurB()));
} else if (del) {
lines.add(new PatchLine(DELETE, hunk.getCurA(), 0));
} else if (ins) {
lines.add(new PatchLine(INSERT, 0, hunk.getCurB()));
}
}
}
lastA = hunk.getCurA();
lastB = hunk.getCurB();
}
if (lastB != b.size()) {
appendSkipLine(nc, b.size() - lastB);
lines.add(new SkippedLine(lastA, lastB, b.size() - lastB));
}
resetHtml(nc);
initScript(script);
for (int row = 0; row < lines.size(); row++) {
setRowItem(row, lines.get(row));
if (lines.get(row) instanceof SkippedLine) {
createSkipLine(row, (SkippedLine) lines.get(row));
}
}
}
private void appendModeLine(final SafeHtmlBuilder nc, final FileMode mode) {
nc.openTd();
nc.setStyleName(Gerrit.RESOURCES.css().lineNumber());
nc.nbsp();
nc.closeTd();
nc.openTd();
nc.addStyleName(Gerrit.RESOURCES.css().fileLine());
nc.addStyleName(Gerrit.RESOURCES.css().fileLineMode());
switch(mode){
case FILE:
nc.nbsp();
break;
case SYMLINK:
nc.append(PatchUtil.C.fileTypeSymlink());
break;
case GITLINK:
nc.append(PatchUtil.C.fileTypeGitlink());
break;
}
nc.closeTd();
}
@Override
public void display(final CommentDetail cd, boolean expandComments) {
if (cd.isEmpty()) {
return;
}
setAccountInfoCache(cd.getAccounts());
for (int row = 0; row < table.getRowCount();) {
if (getRowItem(row) instanceof PatchLine) {
final PatchLine pLine = (PatchLine) getRowItem(row);
final List<PatchLineComment> fora = cd.getForA(pLine.getLineA());
final List<PatchLineComment> forb = cd.getForB(pLine.getLineB());
row++;
final Iterator<PatchLineComment> ai = fora.iterator();
final Iterator<PatchLineComment> bi = forb.iterator();
while (ai.hasNext() && bi.hasNext()) {
final PatchLineComment ac = ai.next();
final PatchLineComment bc = bi.next();
insertRow(row);
bindComment(row, COL_A, ac, !ai.hasNext(), expandComments);
bindComment(row, COL_B, bc, !bi.hasNext(), expandComments);
row++;
}
row = finish(ai, row, COL_A, expandComments);
row = finish(bi, row, COL_B, expandComments);
} else {
row++;
}
}
}
@Override
protected void insertRow(final int row) {
super.insertRow(row);
final CellFormatter fmt = table.getCellFormatter();
fmt.addStyleName(row, COL_A - 1, Gerrit.RESOURCES.css().lineNumber());
fmt.addStyleName(row, COL_A, Gerrit.RESOURCES.css().diffText());
fmt.addStyleName(row, COL_B - 1, Gerrit.RESOURCES.css().lineNumber());
fmt.addStyleName(row, COL_B, Gerrit.RESOURCES.css().diffText());
}
private int finish(final Iterator<PatchLineComment> i, int row, final int col, boolean expandComment) {
while (i.hasNext()) {
final PatchLineComment c = i.next();
insertRow(row);
bindComment(row, col, c, !i.hasNext(), expandComment);
row++;
}
return row;
}
private void appendHeader(PatchScript script, final SafeHtmlBuilder m) {
m.openTr();
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().iconCell());
m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.closeTd();
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.addStyleName(Gerrit.RESOURCES.css().lineNumber());
m.closeTd();
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.setAttribute("width", "50%");
if (script.getChangeType() == ChangeType.RENAMED
|| script.getChangeType() == ChangeType.COPIED) {
m.append(script.getOldName());
} else {
m.append(PatchUtil.C.patchHeaderOld());
}
m.br();
if (0 < script.getA().size()) {
if (idSideA == null) {
downloadLink(m, patchKey, "1");
} else {
downloadLink(m, new Patch.Key(idSideA, patchKey.get()), "0");
}
}
m.closeTd();
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.addStyleName(Gerrit.RESOURCES.css().lineNumber());
m.closeTd();
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.setAttribute("width", "50%");
m.append(PatchUtil.C.patchHeaderNew());
m.br();
if (0 < script.getB().size()) {
downloadLink(m, new Patch.Key(idSideB, patchKey.get()), "0");
}
m.closeTd();
m.closeTr();
}
private void downloadLink(final SafeHtmlBuilder m, final Patch.Key key,
final String side) {
final String base = GWT.getHostPageBaseURL() + "cat/";
m.openAnchor();
m.setAttribute("href", base + KeyUtil.encode(key.toString()) + "^" + side);
m.append(PatchUtil.C.download());
m.closeAnchor();
}
private void appendSkipLine(final SafeHtmlBuilder m, final int skipCnt) {
m.openTr();
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().iconCell());
m.addStyleName(Gerrit.RESOURCES.css().skipLine());
m.closeTd();
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().skipLine());
m.setAttribute("colspan", 4);
m.closeTd();
m.closeTr();
}
ClickHandler expandAllListener = new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
expand(event, 0);
}
};
ClickHandler expandBeforeListener = new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
expand(event, NUM_ROWS_TO_EXPAND);
}
};
ClickHandler expandAfterListener = new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
expand(event, -NUM_ROWS_TO_EXPAND);
}
};
private void expand(ClickEvent event, final int numRows) {
Cell cell = table.getCellForEvent(event);
int row = cell.getRowIndex();
if (!(getRowItem(row) instanceof SkippedLine)) {
return;
}
SkippedLine line = (SkippedLine) getRowItem(row);
int loopTo = numRows;
if (numRows == 0) {
loopTo = line.getSize();
} else if (numRows < 0) {
loopTo = -numRows;
}
int offset = 0;
if (numRows < 0) {
offset = 1;
}
for (int i = 0 + offset; i < loopTo + offset; i++) {
insertRow(row + i);
int lineA = line.getStartA() + i;
int lineB = line.getStartB() + i;
if (numRows < 0) {
lineA = line.getStartA() + line.getSize() + numRows + i - offset;
lineB = line.getStartB() + line.getSize() + numRows + i - offset;
}
setHtml(row + i, 1, "<a href=\"javascript:void(0)\">" + (lineA + 1)
+ "</a>");
addStyle(row + i, 1, Gerrit.RESOURCES.css().lineNumber());
setHtml(row + i, 2, a.getSafeHtmlLine(lineA).asString());
addStyle(row + i, 2, Gerrit.RESOURCES.css().fileLine());
addStyle(row + i, 2, Gerrit.RESOURCES.css().fileLineCONTEXT());
setHtml(row + i, 3, "<a href=\"javascript:void(0)\">" + (lineB + 1)
+ "</a>");
addStyle(row + i, 3, Gerrit.RESOURCES.css().lineNumber());
setHtml(row + i, 4, b.getSafeHtmlLine(lineB).asString());
addStyle(row + i, 4, Gerrit.RESOURCES.css().fileLine());
addStyle(row + i, 4, Gerrit.RESOURCES.css().fileLineCONTEXT());
setRowItem(row + i, new PatchLine(CONTEXT, lineA, lineB));
}
if (numRows > 0) {
line.incrementStart(numRows);
createSkipLine(row + loopTo, line);
} else if (numRows < 0) {
line.reduceSize(-numRows);
createSkipLine(row, line);
} else {
removeRow(row + loopTo);
}
}
private void createSkipLine(int row, SkippedLine line) {
FlowPanel p = new FlowPanel();
Label l1 = new Label(" " + PatchUtil.C.patchSkipRegionStart() + " ");
Anchor all = new Anchor(String.valueOf(line.getSize()));
Label l2 = new Label(" " + PatchUtil.C.patchSkipRegionEnd() + " ");
all.addClickHandler(expandAllListener);
if (line.getSize() > 30) {
// We only show the expand before & after links if we skip more than
// 30 lines.
Anchor before = new Anchor(PatchUtil.M.expandBefore(NUM_ROWS_TO_EXPAND));
before.addClickHandler(expandBeforeListener);
Anchor after = new Anchor(PatchUtil.M.expandAfter(NUM_ROWS_TO_EXPAND));
after.addClickHandler(expandAfterListener);
p.add(before);
p.add(l1);
p.add(all);
p.add(l2);
p.add(after);
} else {
p.add(l1);
p.add(all);
p.add(l2);
}
setWidget(row, 1, p);
}
private void openLine(final SafeHtmlBuilder m) {
m.openTr();
m.setAttribute("valign", "top");
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().iconCell());
m.closeTd();
}
private void appendLineText(final SafeHtmlBuilder m,
final int lineNumberMinusOne, final PatchLine.Type type,
final SparseHtmlFile src, final int i, final boolean fullBlock) {
appendLineText(m, lineNumberMinusOne, type, //
src.getSafeHtmlLine(i), src.hasTrailingEdit(i), fullBlock);
}
private void appendLineText(final SafeHtmlBuilder m,
final int lineNumberMinusOne, final PatchLine.Type type,
final SafeHtml lineHtml, final boolean trailingEdit,
final boolean fullBlock) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.append(SafeHtml.asis("<a href=\"javascript:void(0)\">"+ (lineNumberMinusOne + 1) + "</a>"));
m.closeTd();
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().fileLine());
switch (type) {
case CONTEXT:
m.addStyleName(Gerrit.RESOURCES.css().fileLineCONTEXT());
break;
case DELETE:
m.addStyleName(Gerrit.RESOURCES.css().fileLineDELETE());
if (trailingEdit || fullBlock) {
m.addStyleName("wdd");
}
break;
case INSERT:
m.addStyleName(Gerrit.RESOURCES.css().fileLineINSERT());
if (trailingEdit || fullBlock) {
m.addStyleName("wdi");
}
break;
}
m.append(lineHtml);
m.closeTd();
}
private void appendLineNone(final SafeHtmlBuilder m, final PatchLine.Type type) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.closeTd();
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().fileLine());
switch (type != null ? type : PatchLine.Type.CONTEXT) {
case DELETE:
m.addStyleName(Gerrit.RESOURCES.css().fileLineDELETE());
break;
case INSERT:
m.addStyleName(Gerrit.RESOURCES.css().fileLineINSERT());
break;
default:
m.addStyleName(Gerrit.RESOURCES.css().fileLineNone());
break;
}
m.closeTd();
}
private void closeLine(final SafeHtmlBuilder m) {
m.closeTr();
}
}