blob: 1398708a93fbb322163838c7b5ba7f2cb1447d01 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Alex Blewitt <alex.blewitt@gmail.com> - replace new Boolean with Boolean.valueOf - https://bugs.eclipse.org/470344
*******************************************************************************/
package org.eclipse.compare.internal.merge;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.ICompareFilter;
import org.eclipse.compare.contentmergeviewer.ITokenComparator;
import org.eclipse.compare.internal.CompareContentViewerSwitchingPane;
import org.eclipse.compare.internal.CompareMessages;
import org.eclipse.compare.internal.ComparePreferencePage;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.DocLineComparator;
import org.eclipse.compare.internal.MergeViewerContentProvider;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.internal.core.LCS;
import org.eclipse.compare.rangedifferencer.IRangeComparator;
import org.eclipse.compare.rangedifferencer.RangeDifference;
import org.eclipse.compare.rangedifferencer.RangeDifferencer;
import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
/**
* A document merger manages the differences between two documents
* for either a 2-way or 3-way comparison.
* <p>
* This class should not have any UI dependencies.
*/
public class DocumentMerger {
private static final String DIFF_RANGE_CATEGORY = CompareUIPlugin.PLUGIN_ID + ".DIFF_RANGE_CATEGORY"; //$NON-NLS-1$
/** Selects between smartTokenDiff and mergingTokenDiff */
private static final boolean USE_MERGING_TOKEN_DIFF= false;
/** if true copying conflicts from one side to other concatenates both sides */
private static final boolean APPEND_CONFLICT= true;
/** All diffs for calculating scrolling position (includes line ranges without changes) */
private ArrayList<Diff> fAllDiffs;
/** Subset of above: just real differences. */
private ArrayList<Diff> fChangeDiffs;
private IDocumentMergerInput fInput;
/**
* Interface that defines that input to the document merge process
*/
public interface IDocumentMergerInput {
IDocument getDocument(char contributor);
Position getRegion(char contributor);
boolean isIgnoreAncestor();
boolean isThreeWay();
CompareConfiguration getCompareConfiguration();
ITokenComparator createTokenComparator(String s);
boolean isHunkOnLeft();
int getHunkStart();
boolean isPatchHunk();
boolean isShowPseudoConflicts();
boolean isPatchHunkOk();
}
public class Diff {
/** character range in ancestor document */
Position fAncestorPos;
/** character range in left document */
Position fLeftPos;
/** character range in right document */
Position fRightPos;
/** if this is a TokenDiff fParent points to the enclosing LineDiff */
Diff fParent;
/** if Diff has been resolved */
boolean fResolved;
int fDirection;
boolean fIsToken= false;
/** child token diffs */
List<Diff> fDiffs;
boolean fIsWhitespace= false;
/*
* Create Diff from two ranges and an optional parent diff.
*/
Diff(Diff parent, int dir, IDocument ancestorDoc, Position aRange, int ancestorStart, int ancestorEnd,
IDocument leftDoc, Position lRange, int leftStart, int leftEnd,
IDocument rightDoc, Position rRange, int rightStart, int rightEnd) {
fParent= parent != null ? parent : this;
fDirection= dir;
fLeftPos= createPosition(leftDoc, lRange, leftStart, leftEnd);
fRightPos= createPosition(rightDoc, rRange, rightStart, rightEnd);
if (ancestorDoc != null)
fAncestorPos= createPosition(ancestorDoc, aRange, ancestorStart, ancestorEnd);
}
public Position getPosition(char type) {
switch (type) {
case MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR:
return fAncestorPos;
case MergeViewerContentProvider.LEFT_CONTRIBUTOR:
return fLeftPos;
case MergeViewerContentProvider.RIGHT_CONTRIBUTOR:
return fRightPos;
}
return null;
}
boolean isInRange(char type, int pos) {
Position p= getPosition(type);
return (pos >= p.offset) && (pos < (p.offset+p.length));
}
public String changeType() {
boolean leftEmpty= fLeftPos.length == 0;
boolean rightEmpty= fRightPos.length == 0;
if (fDirection == RangeDifference.LEFT) {
if (!leftEmpty && rightEmpty)
return CompareMessages.TextMergeViewer_changeType_addition;
if (leftEmpty && !rightEmpty)
return CompareMessages.TextMergeViewer_changeType_deletion;
} else {
if (leftEmpty && !rightEmpty)
return CompareMessages.TextMergeViewer_changeType_addition;
if (!leftEmpty && rightEmpty)
return CompareMessages.TextMergeViewer_changeType_deletion;
}
return CompareMessages.TextMergeViewer_changeType_change;
}
public Image getImage() {
int code= Differencer.CHANGE;
switch (fDirection) {
case RangeDifference.RIGHT:
code+= getCompareConfiguration().isMirrored() ? Differencer.RIGHT : Differencer.LEFT;
break;
case RangeDifference.LEFT:
code+= getCompareConfiguration().isMirrored() ? Differencer.LEFT : Differencer.RIGHT ;
break;
case RangeDifference.ANCESTOR:
case RangeDifference.CONFLICT:
code+= Differencer.CONFLICTING;
break;
}
if (code != 0)
return getCompareConfiguration().getImage(code);
return null;
}
Position createPosition(IDocument doc, Position range, int start, int end) {
try {
int l= end-start;
if (range != null) {
int dl= range.length;
if (l > dl)
l= dl;
} else {
int dl= doc.getLength();
if (start+l > dl)
l= dl-start;
}
Position p= null;
try {
p= new Position(start, l);
} catch (RuntimeException ex) {
p= new Position(0, 0);
}
try {
doc.addPosition(DIFF_RANGE_CATEGORY, p);
} catch (BadPositionCategoryException ex) {
// silently ignored
}
return p;
} catch (BadLocationException ee) {
// silently ignored
}
return null;
}
void add(Diff d) {
if (fDiffs == null)
fDiffs= new ArrayList<>();
fDiffs.add(d);
}
public boolean isDeleted() {
if (fAncestorPos != null && fAncestorPos.isDeleted())
return true;
return fLeftPos.isDeleted() || fRightPos.isDeleted();
}
void setResolved(boolean r) {
fResolved= r;
if (r)
fDiffs= null;
}
public boolean isResolved() {
if (!fResolved && fDiffs != null) {
Iterator<Diff> e= fDiffs.iterator();
while (e.hasNext()) {
Diff d= e.next();
if (!d.isResolved())
return false;
}
return true;
}
return fResolved;
}
Position getPosition(int contributor) {
if (contributor == MergeViewerContentProvider.LEFT_CONTRIBUTOR)
return fLeftPos;
if (contributor == MergeViewerContentProvider.RIGHT_CONTRIBUTOR)
return fRightPos;
if (contributor == MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR)
return fAncestorPos;
return null;
}
/*
* Returns true if given character range overlaps with this Diff.
*/
public boolean overlaps(int contributor, int start, int end, int docLength) {
Position h= getPosition(contributor);
if (h != null) {
int ds= h.getOffset();
int de= ds + h.getLength();
if ((start < de) && (end >= ds))
return true;
if ((start == docLength) && (start <= de) && (end >= ds))
return true;
}
return false;
}
public int getMaxDiffHeight() {
Point region= new Point(0, 0);
int h= getLineRange(getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR), fLeftPos, region).y;
if (isThreeWay())
h= Math.max(h, getLineRange(getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR), fAncestorPos, region).y);
return Math.max(h, getLineRange(getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR), fRightPos, region).y);
}
public int getAncestorHeight() {
Point region= new Point(0, 0);
return getLineRange(getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR), fAncestorPos, region).y;
}
public int getLeftHeight() {
Point region= new Point(0, 0);
return getLineRange(getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR), fLeftPos, region).y;
}
public int getRightHeight() {
Point region= new Point(0, 0);
return getLineRange(getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR), fRightPos, region).y;
}
public Diff[] getChangeDiffs(int contributor, IRegion region) {
if (fDiffs != null && intersectsRegion(contributor, region)) {
List<Diff> result = new ArrayList<>();
for (Diff diff : fDiffs) {
if (diff.intersectsRegion(contributor, region)) {
result.add(diff);
}
}
return result.toArray(new Diff[result.size()]);
}
return new Diff[0];
}
private boolean intersectsRegion(int contributor, IRegion region) {
Position p = getPosition(contributor);
if (p != null)
return p.overlapsWith(region.getOffset(), region.getLength());
return false;
}
public boolean hasChildren() {
return fDiffs != null && !fDiffs.isEmpty();
}
public int getKind() {
return fDirection;
}
public boolean isToken() {
return fIsToken;
}
public Diff getParent() {
return fParent;
}
public Iterator<Diff> childIterator() {
if (fDiffs == null)
return new ArrayList<Diff>().iterator();
return fDiffs.iterator();
}
}
public DocumentMerger(IDocumentMergerInput input) {
this.fInput = input;
}
/**
* Perform a two level 2- or 3-way diff.
* The first level is based on line comparison, the second level on token comparison.
* @throws CoreException
*/
public void doDiff() throws CoreException {
fChangeDiffs= new ArrayList<>();
IDocument lDoc = getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR);
IDocument rDoc = getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
if (lDoc == null || rDoc == null)
return;
Position lRegion= getRegion(MergeViewerContentProvider.LEFT_CONTRIBUTOR);
Position rRegion= getRegion(MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
IDocument aDoc = null;
Position aRegion= null;
if (isThreeWay() && !isIgnoreAncestor()) {
aDoc= getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR);
aRegion= getRegion(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR);
}
resetPositions(lDoc);
resetPositions(rDoc);
resetPositions(aDoc);
boolean ignoreWhiteSpace= isIgnoreWhitespace();
ICompareFilter[] compareFilters = getCompareFilters();
DocLineComparator sright = new DocLineComparator(rDoc,
toRegion(rRegion), ignoreWhiteSpace, compareFilters,
MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
DocLineComparator sleft = new DocLineComparator(lDoc,
toRegion(lRegion), ignoreWhiteSpace, compareFilters,
MergeViewerContentProvider.LEFT_CONTRIBUTOR);
DocLineComparator sancestor = null;
if (aDoc != null) {
sancestor = new DocLineComparator(aDoc, toRegion(aRegion),
ignoreWhiteSpace, compareFilters,
MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR);
/*if (isPatchHunk()) {
if (isHunkOnLeft()) {
sright= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
} else {
sleft= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
}
}*/
}
final Object[] result= new Object[1];
final DocLineComparator sa= sancestor, sl= sleft, sr= sright;
IRunnableWithProgress runnable= monitor -> {
monitor.beginTask(CompareMessages.DocumentMerger_0, maxWork(sa, sl, sr));
try {
result[0]= RangeDifferencer.findRanges(monitor, sa, sl, sr);
} catch (OutOfMemoryError ex) {
System.gc();
throw new InvocationTargetException(ex);
}
if (monitor.isCanceled()) { // canceled
throw new InterruptedException();
}
monitor.done();
};
RangeDifference[] e= null;
try {
getCompareConfiguration().getContainer().run(true, true, runnable);
e= (RangeDifference[]) result[0];
} catch (InvocationTargetException ex) {
// we create a NOCHANGE range for the whole document
Diff diff= new Diff(null, RangeDifference.NOCHANGE,
aDoc, aRegion, 0, aDoc != null ? aDoc.getLength() : 0,
lDoc, lRegion, 0, lDoc.getLength(),
rDoc, rRegion, 0, rDoc.getLength());
fAllDiffs = new ArrayList<>();
fAllDiffs.add(diff);
throw new CoreException(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, CompareMessages.DocumentMerger_1, ex.getTargetException()));
} catch (InterruptedException ex) {
// we create a NOCHANGE range for the whole document
Diff diff= new Diff(null, RangeDifference.NOCHANGE,
aDoc, aRegion, 0, aDoc != null ? aDoc.getLength() : 0,
lDoc, lRegion, 0, lDoc.getLength(),
rDoc, rRegion, 0, rDoc.getLength());
fAllDiffs = new ArrayList<>();
fAllDiffs.add(diff);
return;
}
if (isCapped(sa, sl, sr))
fInput.getCompareConfiguration().setProperty(
CompareContentViewerSwitchingPane.OPTIMIZED_ALGORITHM_USED,
Boolean.TRUE);
else
fInput.getCompareConfiguration().setProperty(
CompareContentViewerSwitchingPane.OPTIMIZED_ALGORITHM_USED,
Boolean.FALSE);
ArrayList<Diff> newAllDiffs = new ArrayList<>();
for (RangeDifference es : e) {
int ancestorStart= 0;
int ancestorEnd= 0;
if (sancestor != null) {
ancestorStart= sancestor.getTokenStart(es.ancestorStart());
ancestorEnd= getTokenEnd2(sancestor, es.ancestorStart(), es.ancestorLength());
}
int leftStart= sleft.getTokenStart(es.leftStart());
int leftEnd= getTokenEnd2(sleft, es.leftStart(), es.leftLength());
int rightStart= sright.getTokenStart(es.rightStart());
int rightEnd= getTokenEnd2(sright, es.rightStart(), es.rightLength());
/*if (isPatchHunk()) {
if (isHunkOnLeft()) {
rightStart = rightEnd = getHunkStart();
} else {
leftStart = leftEnd = getHunkStart();
}
}*/
Diff diff= new Diff(null, es.kind(),
aDoc, aRegion, ancestorStart, ancestorEnd,
lDoc, lRegion, leftStart, leftEnd,
rDoc, rRegion, rightStart, rightEnd);
newAllDiffs.add(diff); // remember all range diffs for scrolling
if (isPatchHunk()) {
if (useChange(diff)) {
recordChangeDiff(diff);
}
} else {
if (ignoreWhiteSpace || useChange(es.kind())) {
// Extract the string for each contributor.
String a= null;
if (sancestor != null)
a= extract2(aDoc, sancestor, es.ancestorStart(), es.ancestorLength());
String s= extract2(lDoc, sleft, es.leftStart(), es.leftLength());
String d= extract2(rDoc, sright, es.rightStart(), es.rightLength());
// Indicate whether all contributors are whitespace
if (ignoreWhiteSpace
&& (a == null || a.trim().length() == 0)
&& s.trim().length() == 0
&& d.trim().length() == 0) {
diff.fIsWhitespace= true;
}
// If the diff is of interest, record it and generate the token diffs
if (useChange(diff)) {
recordChangeDiff(diff);
if (s.length() > 0 && d.length() > 0) {
if (a == null && sancestor != null)
a= extract2(aDoc, sancestor, es.ancestorStart(), es.ancestorLength());
if (USE_MERGING_TOKEN_DIFF)
mergingTokenDiff(diff, aDoc, a, rDoc, d, lDoc, s);
else
simpleTokenDiff(diff, aDoc, a, rDoc, d, lDoc, s);
}
}
}
}
}
fAllDiffs = newAllDiffs;
}
private boolean isCapped(DocLineComparator ancestor,
DocLineComparator left, DocLineComparator right) {
if (isCappingDisabled())
return false;
int aLength = ancestor == null? 0 : ancestor.getRangeCount();
int lLength = left.getRangeCount();
int rLength = right.getRangeCount();
if ((double) aLength * (double) lLength > LCS.TOO_LONG
|| (double) aLength * (double) rLength > LCS.TOO_LONG
|| (double) lLength * (double) rLength > LCS.TOO_LONG)
return true;
return false;
}
public Diff findDiff(char type, int pos) throws CoreException {
IDocument aDoc= null;
IDocument lDoc= getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR);
IDocument rDoc= getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
if (lDoc == null || rDoc == null)
return null;
Position aRegion= null;
Position lRegion= null;
Position rRegion= null;
boolean threeWay= isThreeWay();
if (threeWay && !isIgnoreAncestor())
aDoc= getDocument(MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR);
boolean ignoreWhiteSpace= isIgnoreWhitespace();
ICompareFilter[] compareFilters = getCompareFilters();
DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace, compareFilters, MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace, compareFilters, MergeViewerContentProvider.LEFT_CONTRIBUTOR);
DocLineComparator sancestor= null;
if (aDoc != null)
sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace, compareFilters, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR);
final Object[] result= new Object[1];
final DocLineComparator sa= sancestor, sl= sleft, sr= sright;
IRunnableWithProgress runnable= monitor -> {
monitor.beginTask(CompareMessages.DocumentMerger_2, maxWork(sa, sl, sr));
try {
result[0]= RangeDifferencer.findRanges(monitor, sa, sl, sr);
} catch (OutOfMemoryError ex) {
System.gc();
throw new InvocationTargetException(ex);
}
if (monitor.isCanceled()) { // canceled
throw new InterruptedException();
}
monitor.done();
};
RangeDifference[] e= null;
try {
Utilities.executeRunnable(runnable);
e= (RangeDifference[]) result[0];
} catch (InvocationTargetException ex) {
throw new CoreException(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, CompareMessages.DocumentMerger_3, ex.getTargetException()));
} catch (InterruptedException ex) {
//
}
if (e != null) {
for (RangeDifference es : e) {
int kind= es.kind();
int ancestorStart= 0;
int ancestorEnd= 0;
if (sancestor != null) {
ancestorStart= sancestor.getTokenStart(es.ancestorStart());
ancestorEnd= getTokenEnd2(sancestor, es.ancestorStart(), es.ancestorLength());
}
int leftStart= sleft.getTokenStart(es.leftStart());
int leftEnd= getTokenEnd2(sleft, es.leftStart(), es.leftLength());
int rightStart= sright.getTokenStart(es.rightStart());
int rightEnd= getTokenEnd2(sright, es.rightStart(), es.rightLength());
Diff diff= new Diff(null, kind,
aDoc, aRegion, ancestorStart, ancestorEnd,
lDoc, lRegion, leftStart, leftEnd,
rDoc, rRegion, rightStart, rightEnd);
if (diff.isInRange(type, pos))
return diff;
}
}
return null;
}
private void recordChangeDiff(Diff diff) {
fChangeDiffs.add(diff); // here we remember only the real diffs
}
/*private boolean isHunkOnLeft() {
return fInput.isHunkOnLeft();
}
private int getHunkStart() {
return fInput.getHunkStart();
}*/
private boolean isPatchHunk() {
return fInput.isPatchHunk();
}
private boolean isIgnoreWhitespace() {
return Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false);
}
private ICompareFilter[] getCompareFilters() {
return Utilities.getCompareFilters(getCompareConfiguration());
}
private boolean isCappingDisabled() {
return CompareUIPlugin.getDefault().getPreferenceStore().getBoolean(ComparePreferencePage.CAPPING_DISABLED);
}
private IDocument getDocument(char contributor) {
return fInput.getDocument(contributor);
}
private Position getRegion(char contributor) {
return fInput.getRegion(contributor);
}
public boolean isIgnoreAncestor() {
return fInput.isIgnoreAncestor();
}
public boolean isThreeWay() {
return fInput.isThreeWay();
}
/**
* Return the compare configuration associated with this merger.
* @return the compare configuration associated with this merger
*/
public CompareConfiguration getCompareConfiguration() {
return fInput.getCompareConfiguration();
}
/*
* Returns true if kind of change should be shown.
*/
public boolean useChange(Diff diff) {
if (diff.fIsWhitespace)
return false;
int kind = diff.getKind();
return useChange(kind);
}
private boolean useChange(int kind) {
if (kind == RangeDifference.NOCHANGE)
return false;
if (fInput.getCompareConfiguration().isChangeIgnored(kind))
return false;
if (kind == RangeDifference.ANCESTOR)
return fInput.isShowPseudoConflicts();
return true;
}
private int getTokenEnd(ITokenComparator tc, int start, int count) {
if (count <= 0)
return tc.getTokenStart(start);
int index= start + count - 1;
return tc.getTokenStart(index) + tc.getTokenLength(index);
}
private static int getTokenEnd2(ITokenComparator tc, int start, int length) {
return tc.getTokenStart(start + length);
}
/**
* Returns the content of lines in the specified range as a String.
* This includes the line separators.
*
* @param doc the document from which to extract the characters
* @param start index of first line
* @param length number of lines
* @return the contents of the specified line range as a String
*/
private String extract2(IDocument doc, ITokenComparator tc, int start, int length) {
int count= tc.getRangeCount();
if (length > 0 && count > 0) {
//
// int startPos= tc.getTokenStart(start);
// int endPos= startPos;
//
// if (length > 1)
// endPos= tc.getTokenStart(start + (length-1));
// endPos+= tc.getTokenLength(start + (length-1));
//
int startPos= tc.getTokenStart(start);
int endPos;
if (length == 1) {
endPos= startPos + tc.getTokenLength(start);
} else {
endPos= tc.getTokenStart(start + length);
}
try {
return doc.get(startPos, endPos - startPos);
} catch (BadLocationException e) {
// silently ignored
}
}
return ""; //$NON-NLS-1$
}
private static IRegion toRegion(Position position) {
if (position != null)
return new Region(position.getOffset(), position.getLength());
return null;
}
/*
* Performs a "smart" token based 3-way diff on the character range specified by the given baseDiff.
* It is "smart" because it tries to minimize the number of token diffs by merging them.
*/
private void mergingTokenDiff(Diff baseDiff,
IDocument ancestorDoc, String a,
IDocument rightDoc, String d,
IDocument leftDoc, String s) {
ITokenComparator sa= null;
int ancestorStart= 0;
if (ancestorDoc != null) {
sa= createTokenComparator(a);
ancestorStart= baseDiff.fAncestorPos.getOffset();
}
int rightStart= baseDiff.fRightPos.getOffset();
ITokenComparator sm= createTokenComparator(d);
int leftStart= baseDiff.fLeftPos.getOffset();
ITokenComparator sy= createTokenComparator(s);
RangeDifference[] r= RangeDifferencer.findRanges(sa, sy, sm);
for (int i= 0; i < r.length; i++) {
RangeDifference es= r[i];
// determine range of diffs in one line
int start= i;
int leftLine= -1;
int rightLine= -1;
try {
leftLine= leftDoc.getLineOfOffset(leftStart+sy.getTokenStart(es.leftStart()));
rightLine= rightDoc.getLineOfOffset(rightStart+sm.getTokenStart(es.rightStart()));
} catch (BadLocationException e) {
// silently ignored
}
i++;
for (; i < r.length; i++) {
es= r[i];
try {
if (leftLine != leftDoc.getLineOfOffset(leftStart+sy.getTokenStart(es.leftStart())))
break;
if (rightLine != rightDoc.getLineOfOffset(rightStart+sm.getTokenStart(es.rightStart())))
break;
} catch (BadLocationException e) {
// silently ignored
}
}
int end= i;
// find first diff from left
RangeDifference first= null;
for (int ii= start; ii < end; ii++) {
es= r[ii];
if (useChange(es.kind())) {
first= es;
break;
}
}
// find first diff from mine
RangeDifference last= null;
for (int ii= end-1; ii >= start; ii--) {
es= r[ii];
if (useChange(es.kind())) {
last= es;
break;
}
}
if (first != null && last != null) {
int ancestorStart2= 0;
int ancestorEnd2= 0;
if (ancestorDoc != null) {
ancestorStart2= ancestorStart+sa.getTokenStart(first.ancestorStart());
ancestorEnd2= ancestorStart+getTokenEnd(sa, last.ancestorStart(), last.ancestorLength());
}
int leftStart2= leftStart+sy.getTokenStart(first.leftStart());
int leftEnd2= leftStart+getTokenEnd(sy, last.leftStart(), last.leftLength());
int rightStart2= rightStart+sm.getTokenStart(first.rightStart());
int rightEnd2= rightStart+getTokenEnd(sm, last.rightStart(), last.rightLength());
Diff diff= new Diff(baseDiff, first.kind(),
ancestorDoc, null, ancestorStart2, ancestorEnd2,
leftDoc, null, leftStart2, leftEnd2,
rightDoc, null, rightStart2, rightEnd2);
diff.fIsToken= true;
baseDiff.add(diff);
}
}
}
/*
* Performs a token based 3-way diff on the character range specified by the given baseDiff.
*/
private void simpleTokenDiff(final Diff baseDiff,
IDocument ancestorDoc, String a,
IDocument rightDoc, String d,
IDocument leftDoc, String s) {
int ancestorStart= 0;
ITokenComparator sa= null;
if (ancestorDoc != null) {
ancestorStart= baseDiff.fAncestorPos.getOffset();
sa= createTokenComparator(a);
}
int rightStart= baseDiff.fRightPos.getOffset();
ITokenComparator sm= createTokenComparator(d);
int leftStart= baseDiff.fLeftPos.getOffset();
ITokenComparator sy= createTokenComparator(s);
RangeDifference[] e= RangeDifferencer.findRanges(sa, sy, sm);
for (RangeDifference es : e) {
int kind= es.kind();
if (kind != RangeDifference.NOCHANGE) {
int ancestorStart2= ancestorStart;
int ancestorEnd2= ancestorStart;
if (ancestorDoc != null) {
ancestorStart2 += sa.getTokenStart(es.ancestorStart());
ancestorEnd2 += getTokenEnd(sa, es.ancestorStart(), es.ancestorLength());
}
int leftStart2= leftStart + sy.getTokenStart(es.leftStart());
int leftEnd2= leftStart + getTokenEnd(sy, es.leftStart(), es.leftLength());
int rightStart2= rightStart + sm.getTokenStart(es.rightStart());
int rightEnd2= rightStart + getTokenEnd(sm, es.rightStart(), es.rightLength());
Diff diff= new Diff(baseDiff, kind,
ancestorDoc, null, ancestorStart2, ancestorEnd2,
leftDoc, null, leftStart2, leftEnd2,
rightDoc, null, rightStart2, rightEnd2);
// ensure that token diff is smaller than basediff
int leftS= baseDiff.fLeftPos.offset;
int leftE= baseDiff.fLeftPos.offset+baseDiff.fLeftPos.length;
int rightS= baseDiff.fRightPos.offset;
int rightE= baseDiff.fRightPos.offset+baseDiff.fRightPos.length;
if (leftS != leftStart2 || leftE != leftEnd2 ||
rightS != rightStart2 || rightE != rightEnd2) {
diff.fIsToken= true;
// add to base Diff
baseDiff.add(diff);
}
}
}
}
private ITokenComparator createTokenComparator(String s) {
return fInput.createTokenComparator(s);
}
private static int maxWork(IRangeComparator a, IRangeComparator l, IRangeComparator r) {
int ln= l.getRangeCount();
int rn= r.getRangeCount();
if (a != null) {
int an= a.getRangeCount();
return (2 * Math.max(an, ln)) + (2 * Math.max(an, rn));
}
return 2 * Math.max(ln, rn);
}
private void resetPositions(IDocument doc) {
if (doc == null)
return;
try {
doc.removePositionCategory(DIFF_RANGE_CATEGORY);
} catch (BadPositionCategoryException e) {
// Ignore
}
doc.addPositionCategory(DIFF_RANGE_CATEGORY);
}
/*
* Returns the start line and the number of lines which correspond to the given position.
* Starting line number is 0 based.
*/
protected Point getLineRange(IDocument doc, Position p, Point region) {
if (p == null || doc == null) {
region.x= 0;
region.y= 0;
return region;
}
int start= p.getOffset();
int length= p.getLength();
int startLine= 0;
try {
startLine= doc.getLineOfOffset(start);
} catch (BadLocationException e) {
// silently ignored
}
int lineCount= 0;
if (length == 0) {
// // if range length is 0 and if range starts a new line
// try {
// if (start == doc.getLineStartOffset(startLine)) {
// lines--;
// }
// } catch (BadLocationException e) {
// lines--;
// }
} else {
int endLine= 0;
try {
endLine= doc.getLineOfOffset(start + length - 1); // why -1?
} catch (BadLocationException e) {
// silently ignored
}
lineCount= endLine-startLine+1;
}
region.x= startLine;
region.y= lineCount;
return region;
}
public Diff findDiff(Position p, boolean left) {
for (Diff diff : fAllDiffs) {
Position diffPos;
if (left) {
diffPos = diff.fLeftPos;
} else {
diffPos = diff.fRightPos;
}
// If the element falls within a diff, highlight that diff
if (diffPos.offset + diffPos.length >= p.offset && diff.fDirection != RangeDifference.NOCHANGE)
return diff;
// Otherwise, highlight the first diff after the elements position
if (diffPos.offset >= p.offset)
return diff;
}
return null;
}
public void reset() {
fChangeDiffs= null;
fAllDiffs= null;
}
/**
* Returns the virtual position for the given view position.
* @param contributor
* @param vpos
* @return the virtual position for the given view position
*/
public int realToVirtualPosition(char contributor, int vpos) {
if (fAllDiffs == null)
return vpos;
int viewPos= 0; // real view position
int virtualPos= 0; // virtual position
Point region= new Point(0, 0);
Iterator<Diff> e= fAllDiffs.iterator();
while (e.hasNext()) {
Diff diff= e.next();
Position pos= diff.getPosition(contributor);
getLineRange(getDocument(contributor),pos, region);
int realHeight= region.y;
int virtualHeight= diff.getMaxDiffHeight();
if (vpos <= viewPos + realHeight) { // OK, found!
vpos-= viewPos; // make relative to this slot
// now scale position within this slot to virtual slot
if (realHeight <= 0)
vpos= 0;
else
vpos= (vpos*virtualHeight)/realHeight;
return virtualPos+vpos;
}
viewPos+= realHeight;
virtualPos+= virtualHeight;
}
return virtualPos;
}
/**
* maps given virtual position into a real view position of this view.
* @param contributor
* @param v
* @return the real view position
*/
public int virtualToRealPosition(char contributor, int v) {
if (fAllDiffs == null)
return v;
int virtualPos= 0;
int viewPos= 0;
Point region= new Point(0, 0);
Iterator<Diff> e= fAllDiffs.iterator();
while (e.hasNext()) {
Diff diff= e.next();
Position pos= diff.getPosition(contributor);
int viewHeight= getLineRange(getDocument(contributor), pos, region).y;
int virtualHeight= diff.getMaxDiffHeight();
if (v < (virtualPos + virtualHeight)) {
v-= virtualPos; // make relative to this slot
if (viewHeight <= 0) {
v= 0;
} else {
v= (int) (v * ((double)viewHeight/virtualHeight));
}
return viewPos+v;
}
virtualPos+= virtualHeight;
viewPos+= viewHeight;
}
return viewPos;
}
/*
* Calculates virtual height (in lines) of views by adding the maximum of corresponding diffs.
*/
public int getVirtualHeight() {
int h= 1;
if (fAllDiffs != null) {
Iterator<Diff> e= fAllDiffs.iterator();
while (e.hasNext()) {
Diff diff= e.next();
h+= diff.getMaxDiffHeight();
}
}
return h;
}
/*
* Calculates height (in lines) of right view by adding the height of the right diffs.
*/
public int getRightHeight() {
int h= 1;
if (fAllDiffs != null) {
Iterator<Diff> e= fAllDiffs.iterator();
while (e.hasNext()) {
Diff diff= e.next();
h+= diff.getRightHeight();
}
}
return h;
}
public int findInsertionPoint(Diff diff, char type) {
if (diff != null) {
switch (type) {
case MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR:
if (diff.fAncestorPos != null)
return diff.fAncestorPos.offset;
break;
case MergeViewerContentProvider.LEFT_CONTRIBUTOR:
if (diff.fLeftPos != null)
return diff.fLeftPos.offset;
break;
case MergeViewerContentProvider.RIGHT_CONTRIBUTOR:
if (diff.fRightPos != null)
return diff.fRightPos.offset;
break;
}
}
return 0;
}
public Diff[] getChangeDiffs(char contributor, IRegion region) {
if (fChangeDiffs == null)
return new Diff[0];
List<Diff> intersectingDiffs = new ArrayList<>();
for (Diff diff : fChangeDiffs) {
Diff[] changeDiffs = diff.getChangeDiffs(contributor, region);
Collections.addAll(intersectingDiffs, changeDiffs);
}
return intersectingDiffs.toArray(new Diff[intersectingDiffs.size()]);
}
public Diff findDiff(int viewportHeight, boolean synchronizedScrolling, Point size, int my) {
int virtualHeight= synchronizedScrolling ? getVirtualHeight() : getRightHeight();
if (virtualHeight < viewportHeight)
return null;
int yy, hh;
int y= 0;
if (fAllDiffs != null) {
Iterator<Diff> e= fAllDiffs.iterator();
while (e.hasNext()) {
Diff diff= e.next();
int h= synchronizedScrolling ? diff.getMaxDiffHeight()
: diff.getRightHeight();
if (useChange(diff.getKind()) && !diff.fIsWhitespace) {
yy= (y*size.y)/virtualHeight;
hh= (h*size.y)/virtualHeight;
if (hh < 3)
hh= 3;
if (my >= yy && my < yy+hh)
return diff;
}
y+= h;
}
}
return null;
}
public boolean hasChanges() {
return fChangeDiffs != null && !fChangeDiffs.isEmpty();
}
public Iterator<Diff> changesIterator() {
if (fChangeDiffs == null)
return new ArrayList<Diff>().iterator();
return fChangeDiffs.iterator();
}
public Iterator<Diff> rangesIterator() {
if (fAllDiffs == null)
return new ArrayList<Diff>().iterator();
return fAllDiffs.iterator();
}
public boolean isFirstChildDiff(char contributor, int childStart, Diff diff) {
if (!diff.hasChildren())
return false;
Diff d = diff.fDiffs.get(0);
Position p= d.getPosition(contributor);
return (p.getOffset() >= childStart);
}
public Diff getWrappedDiff(Diff diff, boolean down) {
if (fChangeDiffs != null && fChangeDiffs.size() > 0) {
if (down)
return fChangeDiffs.get(0);
return fChangeDiffs.get(fChangeDiffs.size()-1);
}
return null;
}
/*
* Copy the contents of the given diff from one side to the other but
* doesn't reveal anything.
* Returns true if copy was successful.
*/
public boolean copy(Diff diff, boolean leftToRight) {
if (diff != null) {
Position fromPos= null;
Position toPos= null;
IDocument fromDoc= null;
IDocument toDoc= null;
if (leftToRight) {
fromPos= diff.getPosition(MergeViewerContentProvider.LEFT_CONTRIBUTOR);
toPos= diff.getPosition(MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
fromDoc= getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR);
toDoc= getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
} else {
fromPos= diff.getPosition(MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
toPos= diff.getPosition(MergeViewerContentProvider.LEFT_CONTRIBUTOR);
fromDoc= getDocument(MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
toDoc= getDocument(MergeViewerContentProvider.LEFT_CONTRIBUTOR);
}
if (fromDoc != null) {
int fromStart= fromPos.getOffset();
int fromLen= fromPos.getLength();
int toStart= toPos.getOffset();
int toLen= toPos.getLength();
try {
String s= null;
switch (diff.getKind()) {
case RangeDifference.RIGHT:
case RangeDifference.LEFT:
s= fromDoc.get(fromStart, fromLen);
break;
case RangeDifference.ANCESTOR:
break;
case RangeDifference.CONFLICT:
if (APPEND_CONFLICT) {
s= toDoc.get(toStart, toLen);
String ls = TextUtilities.getDefaultLineDelimiter(toDoc);
if (!s.endsWith(ls))
s += ls;
s+= fromDoc.get(fromStart, fromLen);
} else
s= fromDoc.get(fromStart, fromLen);
break;
}
if (s != null) {
toDoc.replace(toStart, toLen, s);
toPos.setOffset(toStart);
toPos.setLength(s.length());
}
} catch (BadLocationException e) {
// silently ignored
}
}
diff.setResolved(true);
return true;
}
return false;
}
public int changesCount() {
if (fChangeDiffs == null)
return 0;
return fChangeDiffs.size();
}
public Diff findDiff(char contributor, int rangeStart, int rangeEnd) {
if (hasChanges()) {
for (Iterator<Diff> iterator = changesIterator(); iterator.hasNext();) {
Diff diff = iterator.next();
if (diff.isDeleted() || diff.getKind() == RangeDifference.NOCHANGE)
continue;
if (diff.overlaps(contributor, rangeStart, rangeEnd, getDocument(contributor).getLength()))
return diff;
}
}
return null;
}
public Diff findDiff(char contributor, Position range) {
int start= range.getOffset();
int end= start + range.getLength();
return findDiff(contributor, start, end);
}
public Diff findNext(char contributor, int start, int end, boolean deep) {
return findNext(contributor, fChangeDiffs, start, end, deep);
}
private Diff findNext(char contributor, List<Diff> v, int start, int end, boolean deep) {
if (v == null)
return null;
for (Diff diff : v) {
Position p= diff.getPosition(contributor);
if (p != null) {
int startOffset= p.getOffset();
if (end < startOffset) // <=
return diff;
if (deep && diff.hasChildren()) {
Diff d= null;
int endOffset= startOffset + p.getLength();
if (start == startOffset && (end == endOffset || end == endOffset-1)) {
d= findNext(contributor, diff.fDiffs, start-1, start-1, deep);
} else if (end < endOffset) {
d= findNext(contributor, diff.fDiffs, start, end, deep);
}
if (d != null)
return d;
}
}
}
return null;
}
public Diff findPrev(char contributor, int start, int end, boolean deep) {
return findPrev(contributor, fChangeDiffs, start, end, deep);
}
private Diff findPrev(char contributor, List<Diff> v, int start, int end, boolean deep) {
if (v == null)
return null;
for (int i= v.size()-1; i >= 0; i--) {
Diff diff= v.get(i);
Position p= diff.getPosition(contributor);
if (p != null) {
int startOffset= p.getOffset();
int endOffset= startOffset + p.getLength();
if (start > endOffset) {
if (deep && diff.hasChildren()) {
// If we are going deep, find the last change in the diff
return findPrev(contributor, diff.fDiffs, end, end, deep);
}
return diff;
}
if (deep && diff.hasChildren()) {
Diff d= null;
if (start == startOffset && end == endOffset) {
// A whole diff is selected so we'll fall through
// and go the the last change in the previous diff
} else if (start >= startOffset) {
// If we are at or before the first diff, select the
// entire diff so next and previous are symmetrical
if (isFirstChildDiff(contributor, start, diff)) {
return diff;
}
d= findPrev(contributor, diff.fDiffs, start, end, deep);
}
if (d != null)
return d;
}
}
}
return null;
}
}