blob: cacedfd106c0073cb19c5d40a69105d2c796e18b [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 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.DisplayMethod;
import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.prettify.client.SparseHtmlFile;
import com.google.gerrit.prettify.common.EditList;
import com.google.gerrit.prettify.common.EditList.Hunk;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
public class UnifiedDiffTable extends AbstractPatchContentTable {
private static final int PC = 3;
private static final Comparator<PatchLineComment> BY_DATE =
new Comparator<PatchLineComment>() {
public int compare(final PatchLineComment o1, final PatchLineComment o2) {
return o1.getWrittenOn().compareTo(o2.getWrittenOn());
}
};
protected boolean isFileCommentBorderRowExist;
// Cursors.
protected int rowOfTableHeaderB;
protected int borderRowOfFileComment;
@Override
protected void onCellDoubleClick(final int row, final int column) {
if (column > C_ARROW && getRowItem(row) instanceof PatchLine) {
final PatchLine pl = (PatchLine) getRowItem(row);
switch (pl.getType()) {
case DELETE:
case CONTEXT:
createCommentEditor(row + 1, PC, pl.getLineA(), (short) 0);
break;
case INSERT:
createCommentEditor(row + 1, PC, pl.getLineB(), (short) 1);
break;
case REPLACE:
break;
}
}
}
@Override
protected void updateCursor(final PatchLineComment newComment) {
if (newComment.getLine() == R_HEAD) {
final PatchSet.Id psId =
newComment.getKey().getParentKey().getParentKey();
switch (newComment.getSide()) {
case FILE_SIDE_A:
if (idSideA == null && idSideB.equals(psId)) {
rowOfTableHeaderB++;
borderRowOfFileComment++;
return;
}
break;
case FILE_SIDE_B:
if (idSideA != null && idSideA.equals(psId)) {
rowOfTableHeaderB++;
borderRowOfFileComment++;
return;
}
if (idSideB.equals(psId)) {
borderRowOfFileComment++;
return;
}
}
}
}
@Override
protected void onCellSingleClick(int row, int column) {
super.onCellSingleClick(row, column);
if (column == 1 || column == 2) {
if (!"".equals(table.getText(row, column))) {
onCellDoubleClick(row, column);
}
}
}
@Override
protected void destroyCommentRow(final int row) {
super.destroyCommentRow(row);
if (this.rowOfTableHeaderB + 1 == row && row + 1 == borderRowOfFileComment) {
table.removeRow(row);
isFileCommentBorderRowExist = false;
}
}
@Override
public void remove(CommentEditorPanel panel) {
super.remove(panel);
if (panel.getComment().getLine() == AbstractPatchContentTable.R_HEAD) {
final PatchSet.Id psId =
panel.getComment().getKey().getParentKey().getParentKey();
switch (panel.getComment().getSide()) {
case FILE_SIDE_A:
if (idSideA == null && idSideB.equals(psId)) {
rowOfTableHeaderB--;
borderRowOfFileComment--;
return;
}
break;
case FILE_SIDE_B:
if (idSideA != null && idSideA.equals(psId)) {
rowOfTableHeaderB--;
borderRowOfFileComment--;
return;
}
if (idSideB.equals(psId)) {
borderRowOfFileComment--;
return;
}
}
}
}
@Override
protected void onInsertComment(final PatchLine pl) {
final int row = getCurrentRow();
switch (pl.getType()) {
case DELETE:
case CONTEXT:
createCommentEditor(row + 1, PC, pl.getLineA(), (short) 0);
break;
case INSERT:
createCommentEditor(row + 1, PC, pl.getLineB(), (short) 1);
break;
case REPLACE:
break;
}
}
private void appendImgTag(SafeHtmlBuilder nc, String url) {
nc.openElement("img");
nc.setAttribute("src", url);
nc.closeElement("img");
}
protected void createFileCommentEditorOnSideA() {
createCommentEditor(R_HEAD + 1, PC, R_HEAD, FILE_SIDE_A);
return;
}
protected void createFileCommentEditorOnSideB() {
createCommentEditor(rowOfTableHeaderB + 1, PC, R_HEAD, FILE_SIDE_B);
createFileCommentBorderRow();
}
private void populateTableHeader(final PatchScript script,
final PatchSetDetail detail) {
initHeaders(script, detail);
table.setWidget(R_HEAD, PC, headerSideA);
table.setWidget(rowOfTableHeaderB, PC, headerSideB);
table.getFlexCellFormatter().addStyleName(R_HEAD, PC,
Gerrit.RESOURCES.css().unifiedTableHeader());
table.getFlexCellFormatter().addStyleName(rowOfTableHeaderB, PC,
Gerrit.RESOURCES.css().unifiedTableHeader());
// Add icons to lineNumber column header
if (headerSideA.isFileOrCommitMessage()) {
table.setWidget(R_HEAD, 1, iconA);
}
if (headerSideB.isFileOrCommitMessage()) {
table.setWidget(rowOfTableHeaderB, 2, iconB);
}
}
private void allocateTableHeader(SafeHtmlBuilder nc) {
rowOfTableHeaderB = 1;
borderRowOfFileComment = 2;
for (int i = R_HEAD; i < borderRowOfFileComment; i++) {
openTableHeaderLine(nc);
padLineNumberOnTableHeaderForSideA(nc);
padLineNumberOnTableHeaderForSideB(nc);
nc.openTd();
nc.setStyleName(Gerrit.RESOURCES.css().fileLine());
nc.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
nc.closeTd();
closeLine(nc);
}
}
@Override
protected void render(final PatchScript script, final PatchSetDetail detail) {
final SafeHtmlBuilder nc = new SafeHtmlBuilder();
allocateTableHeader(nc);
// Display the patch header
for (final String line : script.getPatchHeader()) {
appendFileHeader(nc, line);
}
final ArrayList<PatchLine> lines = new ArrayList<PatchLine>();
if (hasDifferences(script)) {
if (script.getDisplayMethodA() == DisplayMethod.IMG
|| script.getDisplayMethodB() == DisplayMethod.IMG) {
appendImageDifferences(script, nc);
} else if (!isDisplayBinary) {
appendTextDifferences(script, nc, lines);
}
} else {
appendNoDifferences(nc);
}
resetHtml(nc);
populateTableHeader(script, detail);
if (hasDifferences(script)) {
initScript(script);
if (!isDisplayBinary) {
int row = script.getPatchHeader().size();
final CellFormatter fmt = table.getCellFormatter();
final Iterator<PatchLine> iLine = lines.iterator();
while (iLine.hasNext()) {
final PatchLine l = iLine.next();
final String n;
switch (l.getType()) {
case CONTEXT:
n = Gerrit.RESOURCES.css().diffTextCONTEXT();
break;
case DELETE:
n = Gerrit.RESOURCES.css().diffTextDELETE();
break;
case INSERT:
n = Gerrit.RESOURCES.css().diffTextINSERT();
break;
default:
continue;
}
while (!fmt.getStyleName(row, PC).contains(n)) {
row++;
}
setRowItem(row++, l);
}
}
}
}
private void appendImageLine(final SafeHtmlBuilder nc, final String url,
final boolean syntaxHighlighting, final boolean isInsert) {
nc.openTr();
nc.setAttribute("valign", "center");
nc.setAttribute("align", "center");
nc.openTd();
nc.setStyleName(Gerrit.RESOURCES.css().iconCell());
nc.closeTd();
padLineNumberForSideA(nc);
padLineNumberForSideB(nc);
nc.openTd();
nc.setStyleName(Gerrit.RESOURCES.css().fileLine());
if (isInsert) {
setStyleInsert(nc, syntaxHighlighting);
} else {
setStyleDelete(nc, syntaxHighlighting);
}
appendImgTag(nc, url);
nc.closeTd();
nc.closeTr();
}
private void appendImageDifferences(final PatchScript script,
final SafeHtmlBuilder nc) {
final boolean syntaxHighlighting =
script.getDiffPrefs().isSyntaxHighlighting();
if (script.getDisplayMethodA() == DisplayMethod.IMG) {
final String url = getUrlA();
appendImageLine(nc, url, syntaxHighlighting, false);
}
if (script.getDisplayMethodB() == DisplayMethod.IMG) {
final String url = getUrlB();
appendImageLine(nc, url, syntaxHighlighting, true);
}
}
private void appendTextDifferences(final PatchScript script,
final SafeHtmlBuilder nc, final ArrayList<PatchLine> lines) {
final SparseHtmlFile a = getSparseHtmlFileA(script);
final SparseHtmlFile b = getSparseHtmlFileB(script);
final boolean syntaxHighlighting =
script.getDiffPrefs().isSyntaxHighlighting();
for (final EditList.Hunk hunk : script.getHunks()) {
appendHunkHeader(nc, hunk);
while (hunk.next()) {
if (hunk.isContextLine()) {
openLine(nc);
appendLineNumberForSideA(nc, hunk.getCurA());
appendLineNumberForSideB(nc, hunk.getCurB());
appendLineText(nc, false, CONTEXT, a, hunk.getCurA());
closeLine(nc);
hunk.incBoth();
lines.add(new PatchLine(CONTEXT, hunk.getCurA(), hunk.getCurB()));
} else if (hunk.isDeletedA()) {
openLine(nc);
appendLineNumberForSideA(nc, hunk.getCurA());
padLineNumberForSideB(nc);
appendLineText(nc, syntaxHighlighting, DELETE, a, hunk.getCurA());
closeLine(nc);
hunk.incA();
lines.add(new PatchLine(DELETE, hunk.getCurA(), -1));
if (a.size() == hunk.getCurA()
&& script.getA().isMissingNewlineAtEnd()) {
appendNoLF(nc);
}
} else if (hunk.isInsertedB()) {
openLine(nc);
padLineNumberForSideA(nc);
appendLineNumberForSideB(nc, hunk.getCurB());
appendLineText(nc, syntaxHighlighting, INSERT, b, hunk.getCurB());
closeLine(nc);
hunk.incB();
lines.add(new PatchLine(INSERT, -1, hunk.getCurB()));
if (b.size() == hunk.getCurB()
&& script.getB().isMissingNewlineAtEnd()) {
appendNoLF(nc);
}
}
}
}
}
@Override
public void display(final CommentDetail cd, boolean expandComments) {
if (cd.isEmpty()) {
return;
}
setAccountInfoCache(cd.getAccounts());
final ArrayList<PatchLineComment> all = new ArrayList<PatchLineComment>();
for (int row = 0; row < table.getRowCount();) {
final List<PatchLineComment> fora;
final List<PatchLineComment> forb;
if (row == R_HEAD) {
fora = cd.getForA(R_HEAD);
forb = cd.getForB(R_HEAD);
row++;
if (!fora.isEmpty()) {
row = insert(fora, row, expandComments);
}
rowOfTableHeaderB = row;
borderRowOfFileComment = row + 1;
if (!forb.isEmpty()) {
row++;// Skip the Header of sideB.
row = insert(forb, row, expandComments);
borderRowOfFileComment = row;
createFileCommentBorderRow();
}
} else if (getRowItem(row) instanceof PatchLine) {
final PatchLine pLine = (PatchLine) getRowItem(row);
fora = cd.getForA(pLine.getLineA());
forb = cd.getForB(pLine.getLineB());
row++;
if (!fora.isEmpty() && !forb.isEmpty()) {
all.clear();
all.addAll(fora);
all.addAll(forb);
Collections.sort(all, BY_DATE);
row = insert(all, row, expandComments);
} else if (!fora.isEmpty()) {
row = insert(fora, row, expandComments);
} else if (!forb.isEmpty()) {
row = insert(forb, row, expandComments);
}
} else {
row++;
continue;
}
}
}
private void defaultStyle(final int row, final CellFormatter fmt) {
fmt.addStyleName(row, PC - 2, Gerrit.RESOURCES.css().lineNumber());
fmt.addStyleName(row, PC - 2, Gerrit.RESOURCES.css().rightBorder());
fmt.addStyleName(row, PC - 1, Gerrit.RESOURCES.css().lineNumber());
fmt.addStyleName(row, PC, Gerrit.RESOURCES.css().diffText());
}
@Override
protected void insertRow(final int row) {
super.insertRow(row);
final CellFormatter fmt = table.getCellFormatter();
defaultStyle(row, fmt);
}
@Override
protected PatchScreen.Type getPatchScreenType() {
return PatchScreen.Type.UNIFIED;
}
private int insert(final List<PatchLineComment> in, int row, boolean expandComment) {
for (Iterator<PatchLineComment> ci = in.iterator(); ci.hasNext();) {
final PatchLineComment c = ci.next();
if (c.getLine() == R_HEAD) {
insertFileCommentRow(row);
} else {
insertRow(row);
}
bindComment(row, PC, c, !ci.hasNext(), expandComment);
row++;
}
return row;
}
@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, PC - 2, //
Gerrit.RESOURCES.css().cellsNextToFileComment());
fmt.addStyleName(row, PC - 1, //
Gerrit.RESOURCES.css().cellsNextToFileComment());
}
private void createFileCommentBorderRow() {
if (!isFileCommentBorderRowExist) {
isFileCommentBorderRowExist = true;
table.insertRow(borderRowOfFileComment);
final CellFormatter fmt = table.getCellFormatter();
fmt.addStyleName(borderRowOfFileComment, C_ARROW, //
Gerrit.RESOURCES.css().iconCellOfFileCommentRow());
defaultStyle(borderRowOfFileComment, fmt);
final Element iconCell =
fmt.getElement(borderRowOfFileComment, C_ARROW);
UIObject.setStyleName(DOM.getParent(iconCell), //
Gerrit.RESOURCES.css().fileCommentBorder(), true);
}
}
private void appendFileHeader(final SafeHtmlBuilder m, final String line) {
openLine(m);
padLineNumberForSideA(m);
padLineNumberForSideB(m);
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().fileLine());
m.addStyleName(Gerrit.RESOURCES.css().diffText());
m.addStyleName(Gerrit.RESOURCES.css().diffTextFileHeader());
m.append(line);
m.closeTd();
closeLine(m);
}
private void appendHunkHeader(final SafeHtmlBuilder m, final Hunk hunk) {
openLine(m);
padLineNumberForSideA(m);
padLineNumberForSideB(m);
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().fileLine());
m.addStyleName(Gerrit.RESOURCES.css().diffText());
m.addStyleName(Gerrit.RESOURCES.css().diffTextHunkHeader());
m.append("@@ -");
appendRange(m, hunk.getCurA() + 1, hunk.getEndA() - hunk.getCurA());
m.append(" +");
appendRange(m, hunk.getCurB() + 1, hunk.getEndB() - hunk.getCurB());
m.append(" @@");
m.closeTd();
closeLine(m);
}
private void appendRange(final SafeHtmlBuilder m, final int begin,
final int cnt) {
switch (cnt) {
case 0:
m.append(begin - 1);
m.append(",0");
break;
case 1:
m.append(begin);
break;
default:
m.append(begin);
m.append(',');
m.append(cnt);
break;
}
}
private void setStyleDelete(final SafeHtmlBuilder m,
boolean syntaxHighlighting) {
m.addStyleName(Gerrit.RESOURCES.css().diffTextDELETE());
if (syntaxHighlighting) {
m.addStyleName(Gerrit.RESOURCES.css().fileLineDELETE());
}
}
private void setStyleInsert(final SafeHtmlBuilder m,
boolean syntaxHighlighting) {
m.addStyleName(Gerrit.RESOURCES.css().diffTextINSERT());
if (syntaxHighlighting) {
m.addStyleName(Gerrit.RESOURCES.css().fileLineINSERT());
}
}
private void appendLineText(final SafeHtmlBuilder m,
boolean syntaxHighlighting, final PatchLine.Type type,
final SparseHtmlFile src, final int i) {
final SafeHtml text = src.getSafeHtmlLine(i);
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().fileLine());
m.addStyleName(Gerrit.RESOURCES.css().diffText());
switch (type) {
case CONTEXT:
m.addStyleName(Gerrit.RESOURCES.css().diffTextCONTEXT());
m.nbsp();
m.append(text);
break;
case DELETE:
setStyleDelete(m, syntaxHighlighting);
m.append("-");
m.append(text);
break;
case INSERT:
setStyleInsert(m, syntaxHighlighting);
m.append("+");
m.append(text);
break;
case REPLACE:
break;
}
m.closeTd();
}
private void appendNoLF(final SafeHtmlBuilder m) {
openLine(m);
padLineNumberForSideA(m);
padLineNumberForSideB(m);
m.openTd();
m.addStyleName(Gerrit.RESOURCES.css().diffText());
m.addStyleName(Gerrit.RESOURCES.css().diffTextNoLF());
m.append("\\ No newline at end of file");
m.closeTd();
closeLine(m);
}
private void openLine(final SafeHtmlBuilder m) {
m.openTr();
m.setAttribute("valign", "top");
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().iconCell());
m.closeTd();
}
private void openTableHeaderLine(final SafeHtmlBuilder m) {
m.openTr();
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().iconCell());
m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.closeTd();
}
private void closeLine(final SafeHtmlBuilder m) {
m.closeTr();
}
private void padLineNumberForSideB(final SafeHtmlBuilder m) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.closeTd();
}
private void padLineNumberForSideA(final SafeHtmlBuilder m) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.addStyleName(Gerrit.RESOURCES.css().rightBorder());
m.closeTd();
}
private void appendLineNumberForSideB(final SafeHtmlBuilder m, final int idx) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.append(SafeHtml.asis("<a href=\"javascript:void(0)\">"+ (idx + 1) + "</a>"));
m.closeTd();
}
private void appendLineNumberForSideA(final SafeHtmlBuilder m, final int idx) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.addStyleName(Gerrit.RESOURCES.css().rightBorder());
m.append(SafeHtml.asis("<a href=\"javascript:void(0)\">"+ (idx + 1) + "</a>"));
m.closeTd();
}
private void padLineNumberOnTableHeaderForSideB(final SafeHtmlBuilder m) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.closeTd();
}
private void padLineNumberOnTableHeaderForSideA(final SafeHtmlBuilder m) {
m.openTd();
m.setStyleName(Gerrit.RESOURCES.css().lineNumber());
m.addStyleName(Gerrit.RESOURCES.css().fileColumnHeader());
m.addStyleName(Gerrit.RESOURCES.css().rightBorder());
m.closeTd();
}
}