blob: 350c63981d7bf122a41860fce56bf6114c96060c [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.common.data.PatchSetDetail;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.SparseHtmlFile;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import org.eclipse.jgit.diff.Edit;
import java.util.ArrayList;
import java.util.Iterator;
public class SideBySideTable extends AbstractPatchContentTable {
private static final int A = 2;
private static final int B = 3;
private static final int NUM_ROWS_TO_EXPAND = 10;
private SparseHtmlFile a;
private SparseHtmlFile b;
protected boolean isFileCommentBorderRowExist;
protected void createFileCommentEditorOnSideA() {
createCommentEditor(R_HEAD + 1, A, R_HEAD, FILE_SIDE_A);
return;
}
protected void createFileCommentEditorOnSideB() {
createCommentEditor(R_HEAD + 1, B, R_HEAD, FILE_SIDE_B);
return;
}
@Override
protected void onCellDoubleClick(final int row, int column) {
if (column > C_ARROW && getRowItem(row) instanceof PatchLine) {
final PatchLine line = (PatchLine) getRowItem(row);
if (column == 1 || column == A) {
createCommentEditor(row + 1, A, line.getLineA(), (short) 0);
} else if (column == B || column == 4) {
createCommentEditor(row + 1, B, line.getLineB(), (short) 1);
}
}
}
@Override
protected void onCellSingleClick(int row, int column) {
super.onCellSingleClick(row, column);
if (column == 1 || column == 4) {
onCellDoubleClick(row, column);
}
}
@Override
protected void onInsertComment(final PatchLine line) {
final int row = getCurrentRow();
createCommentEditor(row + 1, B, line.getLineB(), (short) 1);
}
@Override
protected void render(final PatchScript script, final PatchSetDetail detail) {
final ArrayList<Object> lines = new ArrayList<Object>();
final SafeHtmlBuilder nc = new SafeHtmlBuilder();
allocateTableHeader(script, nc);
lines.add(null);
if (!isDisplayBinary) {
if (script.getFileModeA() != FileMode.FILE
|| script.getFileModeB() != FileMode.FILE) {
openLine(nc);
appendModeLine(nc, script.getFileModeA());
appendModeLine(nc, script.getFileModeB());
closeLine(nc);
lines.add(null);
}
if (hasDifferences(script)) {
int lastA = 0;
int lastB = 0;
final boolean ignoreWS = script.isIgnoreWhitespace();
a = getSparseHtmlFileA(script);
b = getSparseHtmlFileB(script);
final boolean intraline =
script.getDiffPrefs().isIntralineDifference()
&& script.hasIntralineDifference();
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());
appendLineNumber(nc, hunk.getCurA(), false);
appendLineText(nc, CONTEXT, ctx, false, false);
if (ignoreWS && b.contains(hunk.getCurB())) {
appendLineText(nc, CONTEXT, b, hunk.getCurB(), false);
} else {
appendLineText(nc, CONTEXT, ctx, false, false);
}
appendLineNumber(nc, hunk.getCurB(), true);
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) {
appendLineNumber(nc, hunk.getCurA(), false);
appendLineText(nc, DELETE, a, hunk.getCurA(), full);
hunk.incA();
} else if (hunk.getCurEdit().getType() == Edit.Type.REPLACE) {
appendLineNumber(nc, false);
appendLineNone(nc, DELETE);
} else {
appendLineNumber(nc, false);
appendLineNone(nc, CONTEXT);
}
if (ins) {
appendLineText(nc, INSERT, b, hunk.getCurB(), full);
appendLineNumber(nc, hunk.getCurB(), true);
hunk.incB();
} else if (hunk.getCurEdit().getType() == Edit.Type.REPLACE) {
appendLineNone(nc, INSERT);
appendLineNumber(nc, true);
} else {
appendLineNone(nc, CONTEXT);
appendLineNumber(nc, true);
}
closeLine(nc);
if (del && ins) {
lines.add(new PatchLine(REPLACE, hunk.getCurA(), hunk.getCurB()));
} else if (del) {
lines.add(new PatchLine(DELETE, hunk.getCurA(), -1));
} else if (ins) {
lines.add(new PatchLine(INSERT, -1, 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));
}
}
}else{
// Display the patch header for binary
for (final String line : script.getPatchHeader()) {
appendFileHeader(nc, line);
}
}
if (!hasDifferences(script)) {
appendNoDifferences(nc);
}
resetHtml(nc);
populateTableHeader(script, detail);
if (hasDifferences(script)) {
initScript(script);
if (!isDisplayBinary) {
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), script.getA().isWholeFile());
}
}
}
}
}
private void populateTableHeader(final PatchScript script,
final PatchSetDetail detail) {
initHeaders(script, detail);
table.setWidget(R_HEAD, A, headerSideA);
table.setWidget(R_HEAD, B, headerSideB);
// Populate icons to lineNumber column header.
if (headerSideA.isFileOrCommitMessage()) {
table.setWidget(R_HEAD, A - 1, iconA);
}
if (headerSideB.isFileOrCommitMessage()) {
table.setWidget(R_HEAD, B + 1, iconB);
}
}
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
protected PatchScreen.Type getPatchScreenType() {
return PatchScreen.Type.SIDE_BY_SIDE;
}
@Override
public void display(final CommentDetail cd, boolean expandComments) {
if (cd.isEmpty()) {
return;
}
setAccountInfoCache(cd.getAccounts());
for (int row = 0; row < table.getRowCount();) {
final Iterator<PatchLineComment> ai;
final Iterator<PatchLineComment> bi;
if (row == R_HEAD) {
ai = cd.getForA(R_HEAD).iterator();
bi = cd.getForB(R_HEAD).iterator();
} else if (getRowItem(row) instanceof PatchLine) {
final PatchLine pLine = (PatchLine) getRowItem(row);
ai = cd.getForA(pLine.getLineA()).iterator();
bi = cd.getForB(pLine.getLineB()).iterator();
} else {
row++;
continue;
}
row++;
while (ai.hasNext() && bi.hasNext()) {
final PatchLineComment ac = ai.next();
final PatchLineComment bc = bi.next();
if (ac.getLine() == R_HEAD) {
insertFileCommentRow(row);
} else {
insertRow(row);
}
bindComment(row, A, ac, !ai.hasNext(), expandComments);
bindComment(row, B, bc, !bi.hasNext(), expandComments);
row++;
}
row = finish(ai, row, A, expandComments);
row = finish(bi, row, B, expandComments);
}
}
private void defaultStyle(final int row, final CellFormatter fmt) {
fmt.addStyleName(row, A - 1, Gerrit.RESOURCES.css().lineNumber());
fmt.addStyleName(row, A, Gerrit.RESOURCES.css().diffText());
if (isDisplayBinary) {
fmt.addStyleName(row, A, Gerrit.RESOURCES.css().diffTextForBinaryInSideBySide());
}
fmt.addStyleName(row, B, Gerrit.RESOURCES.css().diffText());
fmt.addStyleName(row, B + 1, Gerrit.RESOURCES.css().lineNumber());
fmt.addStyleName(row, B + 1, Gerrit.RESOURCES.css().rightmost());
}
@Override
protected void insertRow(final int row) {
super.insertRow(row);
final CellFormatter fmt = table.getCellFormatter();
defaultStyle(row, fmt);
}
@Override
protected void insertFileCommentRow(final int row) {
table.insertRow(row);
final CellFormatter fmt = table.getCellFormatter();
fmt.addStyleName(row, C_ARROW, Gerrit.RESOURCES.css().iconCellOfFileCommentRow());
defaultStyle(row, fmt);
fmt.addStyleName(row, C_ARROW, //
Gerrit.RESOURCES.css().cellsNextToFileComment());
fmt.addStyleName(row, A - 1, //
Gerrit.RESOURCES.css().cellsNextToFileComment());
fmt.addStyleName(row, B + 1, //
Gerrit.RESOURCES.css().cellsNextToFileComment());
createFileCommentBorderRow(row);
}
private void createFileCommentBorderRow(final int row) {
if (row == 1 && !isFileCommentBorderRowExist) {
isFileCommentBorderRowExist = true;
table.insertRow(R_HEAD + 2);
final CellFormatter fmt = table.getCellFormatter();
fmt.addStyleName(R_HEAD + 2, C_ARROW, //
Gerrit.RESOURCES.css().iconCellOfFileCommentRow());
defaultStyle(R_HEAD + 2, fmt);
final Element iconCell = fmt.getElement(R_HEAD + 2, C_ARROW);
UIObject.setStyleName(DOM.getParent(iconCell), Gerrit.RESOURCES.css()
.fileCommentBorder(), true);
}
}
private int finish(final Iterator<PatchLineComment> i, int row, final int col, boolean expandComment) {
while (i.hasNext()) {
final PatchLineComment c = i.next();
if (c.getLine() == R_HEAD) {
insertFileCommentRow(row);
} else {
insertRow(row);
}
bindComment(row, col, c, !i.hasNext(), expandComment);
row++;
}
return row;
}
private void allocateTableHeader(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.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.nbsp();
m.closeTd();
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.addStyleName(Gerrit.RESOURCES.css().fileLine());
m.closeTd();
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.addStyleName(Gerrit.RESOURCES.css().fileLine());
m.closeTd();
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.addStyleName(Gerrit.RESOURCES.css().rightmost());
m.closeTd();
m.closeTr();
}
private void appendFileHeader(final SafeHtmlBuilder m, final String line) {
m.openTr();
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().iconCell());
m.closeTd();
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.nbsp();
m.closeTd();
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().sideBySideTableBinaryHeader());
m.setAttribute("colspan", 2);
m.append(line);
m.closeTd();
m.openTd();
m.nbsp();
m.closeTd();
m.closeTr();
}
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();
}
private ClickHandler expandAllListener = new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
expand(event, 0);
}
};
private ClickHandler expandBeforeListener = new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
expand(event, NUM_ROWS_TO_EXPAND);
}
};
private ClickHandler expandAfterListener = new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
expand(event, -NUM_ROWS_TO_EXPAND);
}
};
private void expand(ClickEvent event, final int numRows) {
int row = table.getCellForEvent(event).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;
}
CellFormatter fmt = table.getCellFormatter();
for (int i = 0 + offset; i < loopTo + offset; i++) {
insertRow(row + i);
table.getRowFormatter().setVerticalAlign(row + i,
HasVerticalAlignment.ALIGN_TOP);
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;
}
table.setHTML(row + i, A - 1, "<a href=\"javascript:;\">" + (lineA + 1) + "</a>");
fmt.addStyleName(row + i, A - 1, Gerrit.RESOURCES.css().lineNumber());
table.setHTML(row + i, A, a.getSafeHtmlLine(lineA).asString());
fmt.addStyleName(row + i, A, Gerrit.RESOURCES.css().fileLine());
fmt.addStyleName(row + i, A, Gerrit.RESOURCES.css().fileLineCONTEXT());
table.setHTML(row + i, B, b.getSafeHtmlLine(lineB).asString());
fmt.addStyleName(row + i, B, Gerrit.RESOURCES.css().fileLine());
fmt.addStyleName(row + i, B, Gerrit.RESOURCES.css().fileLineCONTEXT());
table.setHTML(row + i, B + 1, "<a href=\"javascript:;\">" + (lineB + 1) + "</a>");
fmt.addStyleName(row + i, B + 1, Gerrit.RESOURCES.css().lineNumber());
setRowItem(row + i, new PatchLine(CONTEXT, lineA, lineB));
}
if (numRows > 0) {
line.incrementStart(numRows);
// If we got here, we must have the whole file anyway.
createSkipLine(row + loopTo, line, true);
} else if (numRows < 0) {
line.reduceSize(-numRows);
// If we got here, we must have the whole file anyway.
createSkipLine(row, line, true);
} else {
table.removeRow(row + loopTo);
}
}
private void createSkipLine(int row, SkippedLine line, boolean isWholeFile) {
FlowPanel p = new FlowPanel();
InlineLabel l1 = new InlineLabel(" " + PatchUtil.C.patchSkipRegionStart() + " ");
InlineLabel l2 = new InlineLabel(" " + PatchUtil.C.patchSkipRegionEnd() + " ");
Anchor all = new Anchor(String.valueOf(line.getSize()));
all.addClickHandler(expandAllListener);
all.setStyleName(Gerrit.RESOURCES.css().skipLine());
if (line.getSize() > 30 && isWholeFile) {
// Only show the expand before/after if skipped more than 30 lines.
Anchor b = new Anchor(PatchUtil.M.expandBefore(NUM_ROWS_TO_EXPAND), true);
Anchor a = new Anchor(PatchUtil.M.expandAfter(NUM_ROWS_TO_EXPAND), true);
b.addClickHandler(expandBeforeListener);
a.addClickHandler(expandAfterListener);
b.setStyleName(Gerrit.RESOURCES.css().skipLine());
a.setStyleName(Gerrit.RESOURCES.css().skipLine());
p.add(b);
p.add(l1);
p.add(all);
p.add(l2);
p.add(a);
} else if (isWholeFile) {
p.add(l1);
p.add(all);
p.add(l2);
} else {
p.add(l1);
p.add(new InlineLabel(" " + line.getSize() + " "));
p.add(l2);
}
table.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 appendLineNumber(SafeHtmlBuilder m, boolean right) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
if (right) {
m.addStyleName(Gerrit.RESOURCES.css().rightmost());
}
m.closeTd();
}
private void appendLineNumber(SafeHtmlBuilder m, int lineNumberMinusOne, boolean right) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
if (right) {
m.addStyleName(Gerrit.RESOURCES.css().rightmost());
}
m.append(SafeHtml.asis("<a href=\"javascript:;\">"+ (lineNumberMinusOne + 1) + "</a>"));
m.closeTd();
}
private void appendLineText(final SafeHtmlBuilder m,
final PatchLine.Type type, final SparseHtmlFile src, final int i,
final boolean fullBlock) {
appendLineText(m, type, src.getSafeHtmlLine(i), src.hasTrailingEdit(i), fullBlock);
}
private void appendLineText(final SafeHtmlBuilder m,
final PatchLine.Type type, final SafeHtml lineHtml,
final boolean trailingEdit, final boolean fullBlock) {
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;
case REPLACE:
break;
}
m.append(lineHtml);
m.closeTd();
}
private void appendLineNone(final SafeHtmlBuilder m, final PatchLine.Type type) {
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();
}
@Override
protected void destroyCommentRow(final int row) {
super.destroyCommentRow(row);
if (row == R_HEAD + 1) {
table.removeRow(row);
isFileCommentBorderRowExist = false;
}
}
}