| // 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.EditList; |
| import com.google.gerrit.common.data.PatchScript; |
| import com.google.gerrit.common.data.SparseFileContent; |
| import com.google.gerrit.common.data.EditList.Hunk; |
| import com.google.gerrit.common.data.PatchScript.DisplayMethod; |
| import com.google.gerrit.reviewdb.Patch; |
| import com.google.gerrit.reviewdb.PatchLineComment; |
| import com.google.gwt.core.client.GWT; |
| import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; |
| import com.google.gwtexpui.safehtml.client.PrettyFormatter; |
| import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder; |
| import com.google.gwtorm.client.KeyUtil; |
| |
| 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()); |
| } |
| }; |
| |
| @Override |
| protected void onCellDoubleClick(final int row, final int column) { |
| if (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; |
| } |
| } |
| } |
| |
| @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; |
| } |
| } |
| |
| private void appendImgTag(SafeHtmlBuilder nc, String url) { |
| nc.openElement("img"); |
| nc.setAttribute("src", url); |
| nc.closeElement("img"); |
| } |
| |
| @Override |
| protected void render(final PatchScript script) { |
| final SparseFileContent a = script.getA(); |
| final SparseFileContent b = script.getB(); |
| final SafeHtmlBuilder nc = new SafeHtmlBuilder(); |
| final PrettyFormatter fmtA = PrettyFormatter.newFormatter(formatLanguage); |
| final PrettyFormatter fmtB = PrettyFormatter.newFormatter(formatLanguage); |
| |
| fmtB.setShowWhiteSpaceErrors(true); |
| |
| // Display the patch header |
| for (final String line : script.getPatchHeader()) { |
| appendFileHeader(nc, line); |
| } |
| |
| if (script.getDisplayMethodA() == DisplayMethod.IMG |
| || script.getDisplayMethodB() == DisplayMethod.IMG) { |
| final String rawBase = GWT.getHostPageBaseURL() + "cat/"; |
| |
| nc.openTr(); |
| nc.setAttribute("valign", "center"); |
| nc.setAttribute("align", "center"); |
| |
| nc.openTd(); |
| nc.nbsp(); |
| nc.closeTd(); |
| |
| nc.openTd(); |
| nc.nbsp(); |
| nc.closeTd(); |
| |
| nc.openTd(); |
| nc.nbsp(); |
| nc.closeTd(); |
| |
| nc.openTd(); |
| if (script.getDisplayMethodA() == DisplayMethod.IMG) { |
| if (idSideA == null) { |
| appendImgTag(nc, rawBase + KeyUtil.encode(patchKey.toString()) + "^1"); |
| } else { |
| Patch.Key k = new Patch.Key(idSideA, patchKey.get()); |
| appendImgTag(nc, rawBase + KeyUtil.encode(k.toString()) + "^0"); |
| } |
| } |
| if (script.getDisplayMethodB() == DisplayMethod.IMG) { |
| appendImgTag(nc, rawBase + KeyUtil.encode(patchKey.toString()) + "^0"); |
| } |
| nc.closeTd(); |
| |
| nc.closeTr(); |
| } |
| |
| final ArrayList<PatchLine> lines = new ArrayList<PatchLine>(); |
| for (final EditList.Hunk hunk : script.getHunks()) { |
| appendHunkHeader(nc, hunk); |
| while (hunk.next()) { |
| if (hunk.isContextLine()) { |
| openLine(nc); |
| appendLineNumber(nc, hunk.getCurA()); |
| appendLineNumber(nc, hunk.getCurB()); |
| appendLineText(nc, CONTEXT, a, hunk.getCurA(), fmtA, fmtB); |
| closeLine(nc); |
| hunk.incBoth(); |
| lines.add(new PatchLine(CONTEXT, hunk.getCurA(), hunk.getCurB())); |
| |
| } else if (hunk.isDeletedA()) { |
| openLine(nc); |
| appendLineNumber(nc, hunk.getCurA()); |
| padLineNumber(nc); |
| appendLineText(nc, DELETE, a, hunk.getCurA(), fmtA, fmtB); |
| closeLine(nc); |
| hunk.incA(); |
| lines.add(new PatchLine(DELETE, hunk.getCurA(), 0)); |
| if (a.size() == hunk.getCurA() && a.isMissingNewlineAtEnd()) |
| appendNoLF(nc); |
| |
| } else if (hunk.isInsertedB()) { |
| openLine(nc); |
| padLineNumber(nc); |
| appendLineNumber(nc, hunk.getCurB()); |
| appendLineText(nc, INSERT, b, hunk.getCurB(), fmtA, fmtB); |
| closeLine(nc); |
| hunk.incB(); |
| lines.add(new PatchLine(INSERT, 0, hunk.getCurB())); |
| if (b.size() == hunk.getCurB() && b.isMissingNewlineAtEnd()) |
| appendNoLF(nc); |
| } |
| } |
| } |
| resetHtml(nc); |
| initScript(script); |
| |
| 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); |
| } |
| } |
| |
| @Override |
| public void display(final CommentDetail cd) { |
| if (cd.isEmpty()) { |
| return; |
| } |
| setAccountInfoCache(cd.getAccounts()); |
| |
| final ArrayList<PatchLineComment> all = new ArrayList<PatchLineComment>(); |
| 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++; |
| |
| if (!fora.isEmpty() && !forb.isEmpty()) { |
| all.clear(); |
| all.addAll(fora); |
| all.addAll(forb); |
| Collections.sort(all, BY_DATE); |
| row = insert(all, row); |
| |
| } else if (!fora.isEmpty()) { |
| row = insert(fora, row); |
| |
| } else if (!forb.isEmpty()) { |
| row = insert(forb, row); |
| } |
| } else { |
| row++; |
| } |
| } |
| } |
| |
| |
| @Override |
| protected void insertRow(final int row) { |
| super.insertRow(row); |
| final CellFormatter fmt = table.getCellFormatter(); |
| fmt.addStyleName(row, PC - 2, Gerrit.RESOURCES.css().lineNumber()); |
| fmt.addStyleName(row, PC - 1, Gerrit.RESOURCES.css().lineNumber()); |
| fmt.addStyleName(row, PC, Gerrit.RESOURCES.css().diffText()); |
| } |
| |
| private int insert(final List<PatchLineComment> in, int row) { |
| for (Iterator<PatchLineComment> ci = in.iterator(); ci.hasNext();) { |
| final PatchLineComment c = ci.next(); |
| insertRow(row); |
| bindComment(row, PC, c, !ci.hasNext()); |
| row++; |
| } |
| return row; |
| } |
| |
| private void appendFileHeader(final SafeHtmlBuilder m, final String line) { |
| openLine(m); |
| padLineNumber(m); |
| padLineNumber(m); |
| |
| m.openTd(); |
| 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); |
| padLineNumber(m); |
| padLineNumber(m); |
| |
| m.openTd(); |
| 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 appendLineText(final SafeHtmlBuilder m, |
| final PatchLine.Type type, final SparseFileContent src, final int i, |
| final PrettyFormatter fmtA, final PrettyFormatter fmtB) { |
| final String text = src.get(i); |
| m.openTd(); |
| m.addStyleName(Gerrit.RESOURCES.css().diffText()); |
| switch (type) { |
| case CONTEXT: |
| m.addStyleName(Gerrit.RESOURCES.css().diffTextCONTEXT()); |
| m.nbsp(); |
| m.append(fmtA.format(text)); |
| fmtB.update(text); |
| break; |
| case DELETE: |
| m.addStyleName(Gerrit.RESOURCES.css().diffTextDELETE()); |
| m.append("-"); |
| m.append(fmtA.format(text)); |
| break; |
| case INSERT: |
| m.addStyleName(Gerrit.RESOURCES.css().diffTextINSERT()); |
| m.append("+"); |
| m.append(fmtB.format(text)); |
| break; |
| } |
| m.closeTd(); |
| } |
| |
| private void appendNoLF(final SafeHtmlBuilder m) { |
| openLine(m); |
| padLineNumber(m); |
| padLineNumber(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 closeLine(final SafeHtmlBuilder m) { |
| m.closeTr(); |
| } |
| |
| private void padLineNumber(final SafeHtmlBuilder m) { |
| m.openTd(); |
| m.setStyleName(Gerrit.RESOURCES.css().lineNumber()); |
| m.closeTd(); |
| } |
| |
| private void appendLineNumber(final SafeHtmlBuilder m, final int idx) { |
| m.openTd(); |
| m.setStyleName(Gerrit.RESOURCES.css().lineNumber()); |
| m.append(idx + 1); |
| m.closeTd(); |
| } |
| } |