Compare Exploratory Work
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java
index 9cb18d1..659dee7 100644
--- a/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java
@@ -19,12 +19,14 @@
 import java.io.UnsupportedEncodingException;
 import java.lang.reflect.InvocationTargetException;
 import java.util.*;
-import java.util.List;
 
 import org.eclipse.compare.*;
 import org.eclipse.compare.internal.*;
+import org.eclipse.compare.internal.merge.DocumentMerger;
+import org.eclipse.compare.internal.merge.DocumentMerger.Diff;
+import org.eclipse.compare.internal.merge.DocumentMerger.IDocumentMergerInput;
 import org.eclipse.compare.patch.IHunk;
-import org.eclipse.compare.rangedifferencer.*;
+import org.eclipse.compare.rangedifferencer.RangeDifference;
 import org.eclipse.compare.structuremergeviewer.*;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.*;
@@ -32,7 +34,6 @@
 import org.eclipse.jface.action.*;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.resource.ColorRegistry;
 import org.eclipse.jface.resource.JFaceResources;
@@ -54,7 +55,6 @@
 import org.eclipse.swt.widgets.*;
 import org.eclipse.ui.*;
 import org.eclipse.ui.actions.ActionFactory;
-import org.eclipse.ui.progress.IProgressService;
 import org.eclipse.ui.texteditor.*;
 
 import com.ibm.icu.text.MessageFormat;
@@ -161,13 +161,9 @@
 	private static final int BIRDS_EYE_VIEW_INSET= 2;
 	/** */
 	private static final int RESOLVE_SIZE= 5;
-	/** if true copying conflicts from one side to other concatenates both sides */
-	private static final boolean APPEND_CONFLICT= true;
 
 	/** line width of change borders */
 	private static final int LW= 1;
-	/** Selects between smartTokenDiff and mergingTokenDiff */
-	private static final boolean USE_MERGING_TOKEN_DIFF= false;
 		
 	// determines whether a change between left and right is considered incoming or outgoing
 	private boolean fLeftIsLocal;
@@ -202,13 +198,6 @@
 	private IPreferenceStore fPreferenceStore;
 	private IPropertyChangeListener fPreferenceChangeListener;
 	
-	/** All diffs for calculating scrolling position (includes line ranges without changes) */
-	private ArrayList fAllDiffs;
-	/** Subset of above: just real differences. */
-	private ArrayList fChangeDiffs;
-	/** The current diff */
-	private Diff fCurrentDiff;
-	
 	private HashMap fNewAncestorRanges= new HashMap();
 	private HashMap fNewLeftRanges= new HashMap();
 	private HashMap fNewRightRanges= new HashMap();
@@ -291,6 +280,9 @@
 	private TextEditorPropertyAction toggleLineNumbersAction;
 	private IFindReplaceTarget fFindReplaceTarget;
 	private ChangePropertyAction fIgnoreWhitespace;
+	private DocumentMerger fMerger;
+	/** The current diff */
+	private Diff fCurrentDiff;
 
 	private final class InternalOutlineViewerCreator extends OutlineViewerCreator implements ISelectionChangedListener {
 		public Viewer findStructureViewer(Viewer oldViewer,
@@ -340,16 +332,16 @@
 		}
 		
 	    private Diff findDiff(Position p, boolean left) {
-			for (Iterator iterator = fAllDiffs.iterator(); iterator.hasNext();) {
+			for (Iterator iterator = fMerger.rangesIterator(); iterator.hasNext();) {
 				Diff diff = (Diff) iterator.next();
 				Position diffPos;
 				if (left) {
-					diffPos = diff.fLeftPos;
+					diffPos = diff.getPosition(LEFT_CONTRIBUTOR);
 				} else {
-					diffPos = diff.fRightPos;
+					diffPos = diff.getPosition(RIGHT_CONTRIBUTOR);
 				}
 				// If the element falls within a diff, highlight that diff
-				if (diffPos.offset + diffPos.length >= p.offset && diff.fDirection != RangeDifference.NOCHANGE)
+				if (diffPos.offset + diffPos.length >= p.offset && diff.getKind() != RangeDifference.NOCHANGE)
 					return diff;
 				// Otherwise, highlight the first diff after the elements position
 				if (diffPos.offset >= p.offset)
@@ -1028,276 +1020,36 @@
 		}
 	}
 	
-	/*
-	 * A Diff represents synchronized character ranges in two or three Documents.
-	 * The MergeTextViewer uses Diffs to find differences in line and token ranges.
-	 */
-	/* package */ 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 */
-		ArrayList fDiffs;
-		boolean fIsWhitespace= false;
+	private class ChangeHighlighter implements ITextPresentationListener {
 
-		/*
-		 * Create Diff from two ranges and an optional parent diff.
+		private final MergeSourceViewer viewer;
+
+		public ChangeHighlighter(MergeSourceViewer viewer) {
+			this.viewer = viewer;
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation)
 		 */
-		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);
-		}
-		
-		Position getPosition(char type) {
-			switch (type) {
-			case ANCESTOR_CONTRIBUTOR:
-				return fAncestorPos;
-			case LEFT_CONTRIBUTOR:
-				return fLeftPos;
-			case RIGHT_CONTRIBUTOR:
-				return fRightPos;
+		public void applyTextPresentation(TextPresentation textPresentation) {
+			if (!fHighlightTokenChanges)
+				return;
+			IRegion region= textPresentation.getExtent();
+			Diff[] changeDiffs = fMerger.getChangeDiffs(getLeg(viewer), region);
+			for (int i = 0; i < changeDiffs.length; i++) {
+				Diff diff = changeDiffs[i];
+				StyleRange range = getStyleRange(diff, region);
+				if (range != null)
+					textPresentation.mergeStyleRange(range);
 			}
-			return null;
-		}
-		
-		boolean isInRange(char type, int pos) {
-			Position p= getPosition(type);
-			return (pos >= p.offset) && (pos < (p.offset+p.length));
-		}
-		
-		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; 
-		}
-		
-		Image getImage() {
-			int code= Differencer.CHANGE;
-			switch (fDirection) {
-			case RangeDifference.RIGHT:
-				code+= Differencer.LEFT;
-				break;
-			case RangeDifference.LEFT:
-				code+= 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);
-		}
-		
-		boolean isDeleted() {
-			if (fAncestorPos != null && fAncestorPos.isDeleted())
-				return true;
-			return fLeftPos.isDeleted() || fRightPos.isDeleted();
-		}
-		
-		void setResolved(boolean r) {
-			fResolved= r;
-			if (r)
-				fDiffs= null;
-		}
-
-		boolean isResolved() {
-			if (!fResolved && fDiffs != null) {
-				Iterator e= fDiffs.iterator();
-				while (e.hasNext()) {
-					Diff d= (Diff) e.next();
-					if (!d.isResolved())
-						return false;
-				}
-				return true;
-			}
-			return fResolved;
-		}
-		
-//		private boolean isIncoming() {
-//			switch (fDirection) {
-//			case RangeDifference.RIGHT:
-//				if (fLeftIsLocal)
-//					return true;
-//				break;
-//			case RangeDifference.LEFT:
-//				if (!fLeftIsLocal)
-//					return true;
-//				break;
-//			}
-//			return false;
-//		}
-		
-		private boolean isIncomingOrConflicting() {
-			switch (fDirection) {
-			case RangeDifference.RIGHT:
-				if (fLeftIsLocal)
-					return true;
-				break;
-			case RangeDifference.LEFT:
-				if (!fLeftIsLocal)
-					return true;
-				break;
-			case RangeDifference.CONFLICT:
-				return true;
-			}
-			return false;
-		}
-		
-//		private boolean isUnresolvedIncoming() {
-//			if (fResolved)
-//				return false;
-//			return isIncoming();
-//		}
-		
-		private boolean isUnresolvedIncomingOrConflicting() {
-			if (fResolved)
-				return false;
-			return isIncomingOrConflicting();
-		}
-				
-		Position getPosition(MergeSourceViewer w) {
-			if (w == fLeft)
-				return fLeftPos;
-			if (w == fRight)
-				return fRightPos;
-			if (w == fAncestor)
-				return fAncestorPos;
-			return null;
-		}
-		
-		/*
-		 * Returns true if given character range overlaps with this Diff.
-		 */
-		boolean overlaps(MergeSourceViewer w, int start, int end) {
-			Position h= getPosition(w);
-			if (h != null) {
-				int ds= h.getOffset();
-				int de= ds + h.getLength();
-				if ((start < de) && (end >= ds))
-					return true;	
-			}
-			return false;
-		}
-				
-		int getMaxDiffHeight() {
-			Point region= new Point(0, 0);
-			int h= fLeft.getLineRange(fLeftPos, region).y;
-			if (isThreeWay())
-				h= Math.max(h, fAncestor.getLineRange(fAncestorPos, region).y);
-			return Math.max(h, fRight.getLineRange(fRightPos, region).y);
-		}
-		
-		int getAncestorHeight() {
-			Point region= new Point(0, 0);			
-			return fAncestor.getLineRange(fAncestorPos, region).y;
-		}
-
-		int getLeftHeight() {
-			Point region= new Point(0, 0);			
-			return fLeft.getLineRange(fLeftPos, region).y;
-		}
-
-		int getRightHeight() {
-			Point region= new Point(0, 0);			
-			return fRight.getLineRange(fRightPos, region).y;
-		}
-
-		public Diff[] getChangeDiffs(MergeSourceViewer viewer, IRegion region) {
-			if (fDiffs != null && intersectsRegion(viewer, region)) {
-				List result = new ArrayList();
-				for (Iterator iterator = fDiffs.iterator(); iterator.hasNext();) {
-					Diff diff = (Diff) iterator.next();
-					if (diff.intersectsRegion(viewer, region)) {
-						result.add(diff);
-					}
-				}
-				return (Diff[]) result.toArray(new Diff[result.size()]);
-			}
-			return new Diff[0];
-		}
-
-		private boolean intersectsRegion(MergeSourceViewer viewer,
-				IRegion region) {
-			Position p = getPosition(viewer);
-			if (p != null)
-				return p.overlapsWith(region.getOffset(), region.getLength());
-			return false;
-		}
-
-		public StyleRange getStyleRange(MergeSourceViewer viewer, IRegion region) {
+		private StyleRange getStyleRange(Diff diff, IRegion region) {
 			//Color cText = getColor(null, getTextColor());
-			Color cTextFill = getColor(null, getTextFillColor());
+			Color cTextFill = getColor(null, getTextFillColor(diff));
 			if (cTextFill == null)
 				return null;
-			Position p = getPosition(viewer);
+			Position p = diff.getPosition(getLeg(viewer));
 			int start = p.getOffset();
 			int length = p.getLength();
 			// Don't start before the region
@@ -1316,9 +1068,9 @@
 			return new StyleRange(start, length, null, cTextFill);
 		}
 		
-		private RGB getTextFillColor() {
+		private RGB getTextFillColor(Diff diff) {
 			if (isThreeWay() && !isIgnoreAncestor()) {
-				switch (fDirection) {
+				switch (diff.getKind()) {
 				case RangeDifference.RIGHT:
 					if (fLeftIsLocal)
 						return INCOMING_TEXT_FILL;
@@ -1336,54 +1088,6 @@
 			}
 			return OUTGOING_TEXT_FILL;
 		}
-
-		public boolean hasChildren() {
-			return fDiffs != null && !fDiffs.isEmpty();
-		}
-	}
-	
-	private class ChangeHighlighter implements ITextPresentationListener {
-
-		private final MergeSourceViewer viewer;
-
-		public ChangeHighlighter(MergeSourceViewer viewer) {
-			this.viewer = viewer;
-		}
-
-		/* (non-Javadoc)
-		 * @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation)
-		 */
-		public void applyTextPresentation(TextPresentation textPresentation) {
-			if (!fHighlightTokenChanges)
-				return;
-			IRegion region= textPresentation.getExtent();
-			Diff[] changeDiffs = getChangeDiffs(region);
-			for (int i = 0; i < changeDiffs.length; i++) {
-				Diff diff = changeDiffs[i];
-				StyleRange range = getStyleRange(diff, region);
-				if (range != null)
-					textPresentation.mergeStyleRange(range);
-			}
-		}
-
-		private StyleRange getStyleRange(Diff diff, IRegion region) {
-			return diff.getStyleRange(viewer, region);
-		}
-
-		private Diff[] getChangeDiffs(IRegion region) {
-			if (fChangeDiffs == null)
-				return new Diff[0];
-			List intersectingDiffs = new ArrayList();
-			for (Iterator iterator = fChangeDiffs.iterator(); iterator.hasNext();) {
-				Diff diff = (Diff) iterator.next();
-				Diff[] changeDiffs = diff.getChangeDiffs(viewer, region);
-				for (int i = 0; i < changeDiffs.length; i++) {
-					Diff changeDiff = changeDiffs[i];
-					intersectingDiffs.add(changeDiff);
-				}
-			}
-			return (Diff[]) intersectingDiffs.toArray(new Diff[intersectingDiffs.size()]);
-		}
 		
 	}
 	
@@ -1438,6 +1142,58 @@
 	public TextMergeViewer(Composite parent, int style, CompareConfiguration configuration) {
 		super(style, ResourceBundle.getBundle(BUNDLE_NAME), configuration);
 		
+		fMerger = new DocumentMerger(new IDocumentMergerInput() {
+			public ITokenComparator createTokenComparator(String line) {
+				return TextMergeViewer.this.createTokenComparator(line);
+			}
+			public CompareConfiguration getCompareConfiguration() {
+				return TextMergeViewer.this.getCompareConfiguration();
+			}
+			public IDocument getDocument(char contributor) {
+				switch (contributor) {
+				case LEFT_CONTRIBUTOR:
+					return fLeft.getDocument();
+				case RIGHT_CONTRIBUTOR:
+					return fRight.getDocument();
+				case ANCESTOR_CONTRIBUTOR:
+					return fAncestor.getDocument();
+				}
+				return null;
+			}
+			public int getHunkStart() {
+				return TextMergeViewer.this.getHunkStart();
+			}
+			public Position getRegion(char contributor) {
+				switch (contributor) {
+				case LEFT_CONTRIBUTOR:
+					return fLeft.getRegion();
+				case RIGHT_CONTRIBUTOR:
+					return fRight.getRegion();
+				case ANCESTOR_CONTRIBUTOR:
+					return fAncestor.getRegion();
+				}
+				return null;
+			}
+			public boolean isHunkOnLeft() {
+				ITypedElement left = ((ICompareInput)getInput()).getRight();
+				return left != null && Utilities.getAdapter(left, IHunk.class) != null;
+			}
+			public boolean isIgnoreAncestor() {
+				return TextMergeViewer.this.isIgnoreAncestor();
+			}
+			public boolean isPatchHunk() {
+				return TextMergeViewer.this.isPatchHunk();
+			}
+
+			public boolean isShowPseudoConflicts() {
+				return fShowPseudoConflicts;
+			}
+			public boolean isThreeWay() {
+				return TextMergeViewer.this.isThreeWay();
+			}
+			
+		});
+		
 		int inheritedStyle= parent.getStyle();
 		if ((inheritedStyle & SWT.LEFT_TO_RIGHT) != 0)
 			fInheritedDirection= SWT.LEFT_TO_RIGHT;
@@ -1952,7 +1708,7 @@
 				public void mouseMove(MouseEvent e) {
 					Cursor cursor= null;
 					Diff diff= handlemouseInBirdsEyeView(fBirdsEyeCanvas, e.y);
-					if (diff != null && diff.fDirection != RangeDifference.NOCHANGE)
+					if (diff != null && diff.getKind() != RangeDifference.NOCHANGE)
 						cursor= fBirdsEyeCursor;
 					if (fLastCursor != cursor) {
 						fBirdsEyeCanvas.setCursor(cursor);
@@ -1993,7 +1749,7 @@
 	}
 
 	private void setCurrentDiff2(Diff diff, boolean reveal) {
-		if (diff != null && diff.fDirection != RangeDifference.NOCHANGE) {
+		if (diff != null && diff.getKind() != RangeDifference.NOCHANGE) {
 			//fCurrentDiff= null;
 			setCurrentDiff(diff, reveal);
 		}
@@ -2007,20 +1763,20 @@
 		if (! fHighlightRanges)
 			return null;
 
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			int shift= tp.getVerticalScrollOffset() + (2-LW);
 
 			Point region= new Point(0, 0);
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff diff= (Diff) e.next();
+			char leg = getLeg(tp);
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
 				if (diff.isDeleted())
 					continue;
 
 				if (fShowCurrentOnly2 && !isCurrentDiff(diff))
 					continue;
 
-				tp.getLineRange(diff.getPosition(tp), region);
+				tp.getLineRange(diff.getPosition(leg), region);
 				int y= (region.x * lineHeight) + shift;
 				int h= region.y * lineHeight;
 
@@ -2050,26 +1806,25 @@
 		if (! fHighlightRanges)
 			return null;
 
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			int lshift= fLeft.getVerticalScrollOffset();
 			int rshift= fRight.getVerticalScrollOffset();
 
 			Point region= new Point(0, 0);
 
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff diff= (Diff) e.next();
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
 				if (diff.isDeleted())
 					continue;
 
 				if (fShowCurrentOnly2 && !isCurrentDiff(diff))
 					continue;
 
-				fLeft.getLineRange(diff.fLeftPos, region);
+				fLeft.getLineRange(diff.getPosition(LEFT_CONTRIBUTOR), region);
 				int ly= (region.x * lineHeight) + lshift;
 				int lh= region.y * lineHeight;
 
-				fRight.getLineRange(diff.fRightPos, region);
+				fRight.getLineRange(diff.getPosition(RIGHT_CONTRIBUTOR), region);
 				int ry= (region.x * lineHeight) + rshift;
 				int rh= region.y * lineHeight;
 
@@ -2096,35 +1851,7 @@
 	}
 
 	private Diff handlemouseInBirdsEyeView(Canvas canvas, int my) {
-		int yy, hh;
-		
-		Point size= canvas.getSize();
-		
-		int virtualHeight= fSynchronizedScrolling ? getVirtualHeight() : getRightHeight();		
-		if (virtualHeight < getViewportHeight())
-			return null;
-		
-		int y= 0;
-		if (fAllDiffs != null) {
-			Iterator e= fAllDiffs.iterator();
-			for (int i= 0; e.hasNext(); i++) {
-				Diff diff= (Diff) e.next();
-				int h= fSynchronizedScrolling ? diff.getMaxDiffHeight()
-											  : diff.getRightHeight();
-				if (useChange(diff.fDirection) && !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;
+		return fMerger.findDiff(getViewportHeight(), fSynchronizedScrolling, canvas.getSize(), my);
 	}
 	
 	private void paintBirdsEyeView(Canvas canvas, GC gc) {
@@ -2135,54 +1862,50 @@
 		
 		Point size= canvas.getSize();
 		
-		int virtualHeight= fSynchronizedScrolling ? getVirtualHeight() : getRightHeight();		
+		int virtualHeight= fSynchronizedScrolling ? fMerger.getVirtualHeight() : fMerger.getRightHeight();		
 		if (virtualHeight < getViewportHeight())
 			return;
 				
 		Display display= canvas.getDisplay();
 		int y= 0;
-		if (fAllDiffs != null) {
-			Iterator e= fAllDiffs.iterator();
-			for (int i= 0; e.hasNext(); i++) {
-				Diff diff= (Diff) e.next();
-				int h= fSynchronizedScrolling ? diff.getMaxDiffHeight()
-											  : diff.getRightHeight();
-								
-				if (useChange(diff.fDirection) && !diff.fIsWhitespace) {
-					
-					yy= (y*size.y)/virtualHeight;
-					hh= (h*size.y)/virtualHeight;
-					if (hh < 3)
-						hh= 3;
-					
-					c= getColor(display, getFillColor(diff));
-					if (c != null) {
-						gc.setBackground(c);
-						gc.fillRectangle(BIRDS_EYE_VIEW_INSET, yy, size.x-(2*BIRDS_EYE_VIEW_INSET),hh);
-					}
-					c= getColor(display, getStrokeColor(diff));
-					if (c != null) {
-						gc.setForeground(c);
-						r.x= BIRDS_EYE_VIEW_INSET;
-						r.y= yy;
-						r.width= size.x-(2*BIRDS_EYE_VIEW_INSET)-1;
-						r.height= hh;
-						if (diff == fCurrentDiff ||
-								(fCurrentDiff != null && diff == fCurrentDiff.fParent)) {
-							gc.setLineWidth(2);
-							r.x++;
-							r.y++;
-							r.width--;
-							r.height--;
-						} else {
-							gc.setLineWidth(0 /* 1 */);
-						}
-						gc.drawRectangle(r);
-					}
-				}
+		for (Iterator iterator = fMerger.rangesIterator(); iterator.hasNext();) {
+			Diff diff = (Diff) iterator.next();
+			int h= fSynchronizedScrolling ? diff.getMaxDiffHeight()
+										  : diff.getRightHeight();
+							
+			if (fMerger.useChange(diff)) {
 				
-				y+= h;
+				yy= (y*size.y)/virtualHeight;
+				hh= (h*size.y)/virtualHeight;
+				if (hh < 3)
+					hh= 3;
+				
+				c= getColor(display, getFillColor(diff));
+				if (c != null) {
+					gc.setBackground(c);
+					gc.fillRectangle(BIRDS_EYE_VIEW_INSET, yy, size.x-(2*BIRDS_EYE_VIEW_INSET),hh);
+				}
+				c= getColor(display, getStrokeColor(diff));
+				if (c != null) {
+					gc.setForeground(c);
+					r.x= BIRDS_EYE_VIEW_INSET;
+					r.y= yy;
+					r.width= size.x-(2*BIRDS_EYE_VIEW_INSET)-1;
+					r.height= hh;
+					if (isCurrentDiff(diff)) {
+						gc.setLineWidth(2);
+						r.x++;
+						r.y++;
+						r.width--;
+						r.height--;
+					} else {
+						gc.setLineWidth(0 /* 1 */);
+					}
+					gc.drawRectangle(r);
+				}
 			}
+			
+			y+= h;
 		}
 	}
 	
@@ -2257,7 +1980,7 @@
 								setCurrentDiff(fButtonDiff, false);
 								copy(fCurrentDiff, 
 										fCenterButton.getText().equals(COPY_LEFT_TO_RIGHT_INDICATOR), 
-										fCurrentDiff.fDirection != RangeDifference.CONFLICT);
+										fCurrentDiff.getKind() != RangeDifference.CONFLICT);
 							}
 						}
 					}
@@ -2651,9 +2374,9 @@
 					Diff selectDiff= null;
 					if (FIX_47640) {
 						if (leftRange != null)
-						    selectDiff= findDiff(LEFT_CONTRIBUTOR, leftRange);
+						    selectDiff= fMerger.findDiff(LEFT_CONTRIBUTOR, leftRange);
 						else if (rightRange != null)
-						    selectDiff= findDiff(RIGHT_CONTRIBUTOR, rightRange);
+						    selectDiff= fMerger.findDiff(RIGHT_CONTRIBUTOR, rightRange);
 					}
 					if (selectDiff != null)
 						setCurrentDiff(selectDiff, true);
@@ -2673,37 +2396,12 @@
 		return new ContributorInfo(this, element, leg);
 	}
 	
-	private Diff findDiff(char c, Position range) {
-		
-		MergeSourceViewer v;
-		int start= range.getOffset();
-		int end= start + range.getLength();
-		if (c == LEFT_CONTRIBUTOR)
-			v= fLeft;
-		else if (c == RIGHT_CONTRIBUTOR)
-			v= fRight;
-		else
-			return null;
-		
-		if (fChangeDiffs != null) {
-			Iterator iter= fChangeDiffs.iterator();
-			while (iter.hasNext()) {
-				Diff diff= (Diff) iter.next();
-				if (diff.isDeleted() || diff.fDirection == RangeDifference.NOCHANGE)
-				    continue;
-				if (diff.overlaps(v, start, end))
-					return diff;
-			}
-		}
-		return null;
-	}
-	
 	private void updateDiffBackground(Diff diff) {
 		
 		if (! fHighlightRanges)
 			return;
 		
-		if (diff == null || diff.fIsToken)
+		if (diff == null || diff.isToken())
 			return;
 			
 		if (fShowCurrentOnly && !isCurrentDiff(diff))
@@ -2714,36 +2412,25 @@
 			return;
 			
 		if (isThreeWay())
-			fAncestor.setLineBackground(diff.fAncestorPos, c);
-		fLeft.setLineBackground(diff.fLeftPos, c);
-		fRight.setLineBackground(diff.fRightPos, c);
+			fAncestor.setLineBackground(diff.getPosition(ANCESTOR_CONTRIBUTOR), c);
+		fLeft.setLineBackground(diff.getPosition(LEFT_CONTRIBUTOR), c);
+		fRight.setLineBackground(diff.getPosition(RIGHT_CONTRIBUTOR), c);
 	}
 	
 	private void updateAllDiffBackgrounds(Display display) {
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			boolean threeWay= isThreeWay();
-			Iterator iter= fChangeDiffs.iterator();
-			while (iter.hasNext()) {
-				Diff diff= (Diff) iter.next();
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
 				Color c= getColor(display, getFillColor(diff));
 				if (threeWay)
-					fAncestor.setLineBackground(diff.fAncestorPos, c);
-				fLeft.setLineBackground(diff.fLeftPos, c);
-				fRight.setLineBackground(diff.fRightPos, c);
+					fAncestor.setLineBackground(diff.getPosition(ANCESTOR_CONTRIBUTOR), c);
+				fLeft.setLineBackground(diff.getPosition(LEFT_CONTRIBUTOR), c);
+				fRight.setLineBackground(diff.getPosition(RIGHT_CONTRIBUTOR), c);
 			}
 		}
 	}
 	
-	boolean isCurrentDiff(Diff diff) {
-		if (diff == null)
-			return false;
-		if (diff == fCurrentDiff)
-			return true;
-		if (fCurrentDiff != null && fCurrentDiff.fParent == diff)
-			return true;
-		return false;
-	}
-	
 	/*
 	 * Called whenever one of the documents changes.
 	 * Sets the dirty state of this viewer and updates the lines.
@@ -2810,22 +2497,7 @@
 			IDocumentRange dr= (IDocumentRange) other;
 			Position p= dr.getRange();	
 			Diff diff= findDiff(otherType, p.offset);
-			if (diff != null) {
-				switch (type) {
-				case ANCESTOR_CONTRIBUTOR:
-					if (diff.fAncestorPos != null)
-						return diff.fAncestorPos.offset;
-					break;
-				case LEFT_CONTRIBUTOR:
-					if (diff.fLeftPos != null)
-						return diff.fLeftPos.offset;
-					break;
-				case RIGHT_CONTRIBUTOR:
-					if (diff.fRightPos != null)
-						return diff.fRightPos.offset;
-					break;
-				}
-			}
+			return fMerger.findInsertionPoint(diff, type);
 		}
 		return 0;
 	}
@@ -3059,224 +2731,53 @@
 	}
 	
 	//---- the differencing
-
-	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);
-	}
 	
 	/**
 	 * Perform a two level 2- or 3-way diff.
 	 * The first level is based on line comparison, the second level on token comparison.
 	 */
 	private void doDiff() {
-						
-		ArrayList newAllDiffs = new ArrayList();
-		fChangeDiffs= new ArrayList();
-		fCurrentDiff= null;
-		
-		IDocument aDoc= null;
 		IDocument lDoc= fLeft.getDocument();
 		IDocument rDoc= fRight.getDocument();
 		if (lDoc == null || rDoc == null)
 			return;
-			
-		Position aRegion= null;
-		Position lRegion= fLeft.getRegion();
-		Position rRegion= fRight.getRegion();
-				
-		boolean threeWay= isThreeWay();
-		
-		if (threeWay && !isIgnoreAncestor()) {
-			aDoc= fAncestor.getDocument();
-			aRegion= fAncestor.getRegion();
-		}
-		
-		resetPositions(lDoc);
-		resetPositions(rDoc);
-		resetPositions(aDoc);
-		
 		fAncestor.resetLineBackground();
 		fLeft.resetLineBackground();
 		fRight.resetLineBackground();
 		
-		boolean ignoreWhiteSpace= Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false);		
-		
-		DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace);
-		DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace);
-		DocLineComparator sancestor= null;
-		boolean isRight = true;
-		if (aDoc != null) {
-			sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
-			if (isPatchHunk()) {
-				ITypedElement right = ((ICompareInput)getInput()).getRight();
-				isRight = right != null && Utilities.getAdapter(right, IHunk.class) != null;
-				if (isRight) {
-					sleft= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
-				} else {
-					sright= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
-				}
-			}
-		}
-			
-		if (!fSubDoc && rRegion != null && lRegion != null) {
-			// we have to add a diff for the ignored lines
-			
-			int astart= 0;
-			int as= 0;
-			if (aRegion != null) {
-				astart= aRegion.getOffset();
-				as= Math.max(0, astart-1);
-			}
-			int ys= Math.max(0, lRegion.getOffset()-1);
-			int ms= Math.max(0, rRegion.getOffset()-1);
-			
-			if (as > 0 || ys > 0 || ms > 0) {
-				Diff diff= new Diff(null, RangeDifference.NOCHANGE,
-					aDoc, aRegion, 0, astart,
-					lDoc, lRegion, 0, lRegion.getOffset(),
-					rDoc, rRegion, 0, rRegion.getOffset());
-				newAllDiffs.add(diff);
-			}
-		}
-		
-		final ResourceBundle bundle= getResourceBundle();
-			
-		final Object[] result= new Object[1];
-		final DocLineComparator sa= sancestor, sl= sleft, sr= sright;
-		IRunnableWithProgress runnable= new IRunnableWithProgress() {
-			public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
-				String progressTitle= Utilities.getString(bundle, "compareProgressTask.title"); //$NON-NLS-1$
-				monitor.beginTask(progressTitle, 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;
+		fCurrentDiff= null;
 		try {
-			getCompareConfiguration().getContainer().run(true, true, runnable);
-			e= (RangeDifference[]) result[0];
-		} catch (InvocationTargetException ex) {
-			String title= Utilities.getString(bundle, "tooComplexError.title"); //$NON-NLS-1$
-			String format= Utilities.getString(bundle, "tooComplexError.format"); //$NON-NLS-1$
+			fMerger.doDiff();
+		} catch (CoreException e) {
+			CompareUIPlugin.log(e.getStatus());
+			String title= Utilities.getString(getResourceBundle(), "tooComplexError.title"); //$NON-NLS-1$
+			String format= Utilities.getString(getResourceBundle(), "tooComplexError.format"); //$NON-NLS-1$
 			String msg= MessageFormat.format(format, new Object[] { Integer.toString(PlatformUI.getWorkbench().getProgressService().getLongOperationTime()/1000) } );
 			MessageDialog.openError(fComposite.getShell(), title, msg);
-			e= null;
-		} catch (InterruptedException ex) {
-			// 
 		}
-					
-		if (e == null) {
-			// 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());	
-				
-			newAllDiffs.add(diff);
-		} else {
-			for (int i= 0; i < e.length; i++) {
-				String a= null, s= null, d= null;
-				RangeDifference es= e[i];
-				
-				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());
-				
-				if (isPatchHunk()) {
-					if (isRight)
-						leftStart = leftEnd = getHunkStart();
-					else
-						rightStart = rightEnd = getHunkStart();
-				}
-				
-				Diff diff= new Diff(null, kind,
-					aDoc, aRegion, ancestorStart, ancestorEnd,
-					lDoc, lRegion, leftStart, leftEnd,
-					rDoc, rRegion, rightStart, rightEnd);	
-				
-				newAllDiffs.add(diff);	// remember all range diffs for scrolling
 		
-				if (ignoreWhiteSpace && !isPatchHunk()) {
-					if (sancestor != null)
-						a= extract2(aDoc, sancestor, es.ancestorStart(), es.ancestorLength());
-					s= extract2(lDoc, sleft, es.leftStart(), es.leftLength());
-					d= extract2(rDoc, sright, es.rightStart(), es.rightLength());
-				
-					if ((a == null || a.trim().length() == 0) && s.trim().length() == 0 && d.trim().length() == 0) {
-						diff.fIsWhitespace= true;
-						continue;
-					}
-				}
-		
-				if (useChange(kind)) {
-					fChangeDiffs.add(diff);	// here we remember only the real diffs
-					updateDiffBackground(diff);
-		
-					// Only do the token diff for non-hunks
-					if (!isPatchHunk()) {
-						if (s == null)
-							s= extract2(lDoc, sleft, es.leftStart(), es.leftLength());
-						if (d == null)
-							d= extract2(rDoc, sright, es.rightStart(), es.rightLength());
-						
-						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);
-						}
-					}
-				}
+		if (fMerger.hasChanges()) {
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
+				updateDiffBackground(diff);
 			}
 		}
-		
-		if (!fSubDoc && rRegion != null && lRegion != null) {
-			// we have to add a diff for the ignored lines
-			
-			int aEnd= 0;
-			int aLen= 0;
-			if (aRegion != null && aDoc != null) {
-				aEnd= aRegion.getOffset()+aRegion.getLength();
-				aLen= aDoc.getLength();
-			}
-			Diff diff= new Diff(null, RangeDifference.NOCHANGE,
-				aDoc, aRegion, aEnd, aLen,
-				lDoc, lRegion, lRegion.getOffset()+lRegion.getLength(), lDoc.getLength(),
-				rDoc, rRegion, rRegion.getOffset()+rRegion.getLength(), rDoc.getLength());
-			newAllDiffs.add(diff);
-		}
-		fAllDiffs = newAllDiffs;
 		invalidateTextPresentation();
 	}
 	
+	private Diff findDiff(char type, int pos) {
+		try {
+			return fMerger.findDiff(type, pos);
+		} catch (CoreException e) {
+			CompareUIPlugin.log(e.getStatus());
+			String title= Utilities.getString(getResourceBundle(), "tooComplexError.title"); //$NON-NLS-1$
+			String format= Utilities.getString(getResourceBundle(), "tooComplexError.format"); //$NON-NLS-1$
+			String msg= MessageFormat.format(format, new Object[] { Integer.toString(PlatformUI.getWorkbench().getProgressService().getLongOperationTime()/1000) } );
+			MessageDialog.openError(fComposite.getShell(), title, msg);
+			return null;
+		}
+	}
+	
 	private void resetPositions(IDocument doc) {
 		if (doc == null)
 			return;
@@ -3287,314 +2788,6 @@
 		}
 		doc.addPositionCategory(DIFF_RANGE_CATEGORY);
 	}
-
-	private Diff findDiff(char type, int pos) {
-								
-		IDocument aDoc= null;
-		IDocument lDoc= fLeft.getDocument();
-		IDocument rDoc= fRight.getDocument();
-		if (lDoc == null || rDoc == null)
-			return null;
-			
-		Position aRegion= null;
-		Position lRegion= null;
-		Position rRegion= null;
-				
-		boolean threeWay= isThreeWay();
-		
-		if (threeWay && !isIgnoreAncestor())
-			aDoc= fAncestor.getDocument();
-
-		boolean ignoreWhiteSpace= Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false);		
-		
-		DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace);
-		DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace);
-		DocLineComparator sancestor= null;
-		if (aDoc != null)
-			sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
-			
-		final ResourceBundle bundle= getResourceBundle();
-			
-		final Object[] result= new Object[1];
-		final DocLineComparator sa= sancestor, sl= sleft, sr= sright;
-		IRunnableWithProgress runnable= new IRunnableWithProgress() {
-			public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
-				String progressTitle= Utilities.getString(bundle, "compareProgressTask.title"); //$NON-NLS-1$
-				monitor.beginTask(progressTitle, 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();
-			}
-		};
-		IProgressService progressService= PlatformUI.getWorkbench().getProgressService();
-		
-		RangeDifference[] e= null;
-		try {
-			progressService.run(true, true, runnable);
-			e= (RangeDifference[]) result[0];
-		} catch (InvocationTargetException ex) {
-			String title= Utilities.getString(bundle, "tooComplexError.title"); //$NON-NLS-1$
-			String format= Utilities.getString(bundle, "tooComplexError.format"); //$NON-NLS-1$
-			String msg= MessageFormat.format(format, new Object[] { Integer.toString(progressService.getLongOperationTime()/1000) } );
-			MessageDialog.openError(fComposite.getShell(), title, msg);
-			e= null;
-		} catch (InterruptedException ex) {
-			// 
-		}
-					
-		if (e != null) {
-			for (int i= 0; i < e.length; i++) {
-				RangeDifference es= e[i];
-				
-				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;
-	}
-	
-	/*
-	 * Returns true if kind of change should be shown.
-	 */
-	private boolean useChange(int kind) {
-		if (kind == RangeDifference.NOCHANGE)
-			return false;
-		if (kind == RangeDifference.ANCESTOR)
-			return fShowPseudoConflicts;
-		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$
-	}
-
-	/*
-	 * 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 (int i= 0; i < e.length; i++) {
-			RangeDifference es= e[i];
-			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);
-				}
-			}
-		}
-	}
-	
-	/*
-	 * 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);
-			}
-		}
-	}
 	
 	//---- update UI stuff
 	
@@ -3629,16 +2822,8 @@
 		if (fCopyDiffRightToLeftItem != null)
 			((Action)fCopyDiffRightToLeftItem.getAction()).setEnabled(rightToLeft);
 			
-		boolean enableNavigation= false;
-		if (fCurrentDiff == null && fChangeDiffs != null && fChangeDiffs.size() > 0)
-			enableNavigation= true;
-		else if (fChangeDiffs != null && fChangeDiffs.size() > 1)
-			enableNavigation= true;
-		else if (fCurrentDiff != null && fCurrentDiff.fDiffs != null)
-			enableNavigation= true;
-		else if (fCurrentDiff != null && fCurrentDiff.fIsToken)
-			enableNavigation= true;
-		
+		boolean enableNavigation= isNavigationPossible();
+
 		if (fNextDiff != null) {
 			IAction a= fNextDiff.getAction();
 			a.setEnabled(enableNavigation || hasNextElement(true));
@@ -3667,14 +2852,14 @@
 			int unresolvedIncoming= 0;
 			int unresolvedConflicting= 0;
 
-			if (fChangeDiffs != null) {
-				Iterator e= fChangeDiffs.iterator();
-				while (e.hasNext()) {
-					Diff d= (Diff) e.next();
+			if (fMerger.hasChanges()) {
+				for (Iterator iterator = fMerger.changesIterator(); iterator
+						.hasNext();) {
+					Diff d = (Diff) iterator.next();
 					if (d.isIncomingOrConflicting() /* && useChange(d.fDirection) && !d.fIsWhitespace */) {
 						incomingOrConflicting++;
-						if (!d.fResolved) {
-							if (d.fDirection == RangeDifference.CONFLICT) {
+						if (!d.isResolved()) {
+							if (d.getKind() == RangeDifference.CONFLICT) {
 								unresolvedConflicting++;
 								break; // we can stop here because a conflict has the maximum priority
 							}
@@ -3709,16 +2894,16 @@
 			diffDescription= CompareMessages.TextMergeViewer_diffDescription_noDiff_format;	
 		} else {
 			
-			if (diff.fIsToken)		// we don't show special info for token diffs
-				diff= diff.fParent;
+			if (diff.isToken())		// we don't show special info for token diffs
+				diff= diff.getParent();
 		
 			String format= CompareMessages.TextMergeViewer_diffDescription_diff_format;	
 			diffDescription= MessageFormat.format(format, 
 				new String[] {
 					getDiffType(diff),						// 0: diff type
 					getDiffNumber(diff),					// 1: diff number
-					getDiffRange(fLeft, diff.fLeftPos),		// 2: left start line
-					getDiffRange(fRight, diff.fRightPos)	// 3: left end line
+					getDiffRange(fLeft, diff.getPosition(LEFT_CONTRIBUTOR)),		// 2: left start line
+					getDiffRange(fRight, diff.getPosition(RIGHT_CONTRIBUTOR))	// 3: left end line
 				}
 			);
 		}
@@ -3741,7 +2926,7 @@
 	
 	private String getDiffType(Diff diff) {
 		String s= ""; 	//$NON-NLS-1$
-		switch(diff.fDirection) {
+		switch(diff.getKind()) {
 		case RangeDifference.LEFT:
 			s= CompareMessages.TextMergeViewer_direction_outgoing;	
 			break;
@@ -3759,10 +2944,9 @@
 	private String getDiffNumber(Diff diff) {
 		// find the diff's number
 		int diffNumber= 0;
-		if (fChangeDiffs != null) {
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff d= (Diff) e.next();
+		if (fMerger.hasChanges()) {
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff d = (Diff) iterator.next();
 				diffNumber++;
 				if (d == diff)
 					break;
@@ -4036,9 +3220,9 @@
 		
 		Diff firstDiff= null;
 		if (first)
-			firstDiff= findNext(fRight, fChangeDiffs, -1, -1, false);
+			firstDiff= findNext(fRight, -1, -1, false);
 		else
-			firstDiff= findPrev(fRight, fChangeDiffs, 9999999, 9999999, false);
+			firstDiff= findPrev(fRight, 9999999, 9999999, false);
 		setCurrentDiff(firstDiff, true);
 	}
 	
@@ -4189,26 +3373,25 @@
 
 		boolean showResolveUI= showResolveUI();
 
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			int lshift= fLeft.getVerticalScrollOffset();
 			int rshift= fRight.getVerticalScrollOffset();
 					
 			Point region= new Point(0, 0);
 		
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff diff= (Diff) e.next();
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
 				if (diff.isDeleted())
 					continue;
 				
 				if (fShowCurrentOnly2 && !isCurrentDiff(diff))
 					continue;
 
-				fLeft.getLineRange(diff.fLeftPos, region);
+				fLeft.getLineRange(diff.getPosition(LEFT_CONTRIBUTOR), region);
 				int ly= (region.x * lineHeightLeft) + lshift;
 				int lh= region.y * lineHeightLeft;
 	
-				fRight.getLineRange(diff.fRightPos, region);
+				fRight.getLineRange(diff.getPosition(RIGHT_CONTRIBUTOR), region);
 				int ry= (region.x * lineHeightRight) + rshift;
 				int rh= region.y * lineHeightRight;
 	
@@ -4336,20 +3519,20 @@
 		if (! fHighlightRanges)
 			return;
 
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			int shift= tp.getVerticalScrollOffset() + (2-LW);
 				
 			Point region= new Point(0, 0);
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff diff= (Diff) e.next();
+			char leg = getLeg(tp);
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
 				if (diff.isDeleted())
 					continue;
 				
 				if (fShowCurrentOnly2 && !isCurrentDiff(diff))
 					continue;
 
-				tp.getLineRange(diff.getPosition(tp), region);	
+				tp.getLineRange(diff.getPosition(leg), region);	
 				int y= (region.x * lineHeight) + shift;
 				int h= region.y * lineHeight;
 	
@@ -4378,7 +3561,7 @@
 		
 		if (! fHighlightRanges)
 			return;
-		if (fChangeDiffs == null)
+		if (!fMerger.hasChanges())
 			return;
 
 		Control canvas= (Control) event.widget;
@@ -4395,17 +3578,17 @@
 			shift+= fTopInset;
 				
 		Point range= new Point(0, 0);
-				
-		Iterator e= fChangeDiffs.iterator();	
-		while (e.hasNext()) {
-			Diff diff= (Diff) e.next();
+		
+		char leg = getLeg(tp);
+		for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+			Diff diff = (Diff) iterator.next();
 			if (diff.isDeleted())
 				continue;
 			
 			if (fShowCurrentOnly && !isCurrentDiff(diff))
 				continue;
 
-			tp.getLineRange(diff.getPosition(tp), range);
+			tp.getLineRange(diff.getPosition(leg), range);
 			int y= (range.x * lineHeight) + shift;
 			int h= range.y * lineHeight;
 			
@@ -4421,10 +3604,10 @@
 	}
 
 	private RGB getFillColor(Diff diff) {
-		boolean selected= fCurrentDiff != null && fCurrentDiff.fParent == diff;
+		boolean selected= fCurrentDiff != null && fCurrentDiff.getParent() == diff;
 		RGB selected_fill= getBackground(null);
 		if (isThreeWay() && !isIgnoreAncestor()) {
-			switch (diff.fDirection) {
+			switch (diff.getKind()) {
 			case RangeDifference.RIGHT:
 				if (fLeftIsLocal)
 					return selected ? selected_fill : INCOMING_FILL;
@@ -4444,10 +3627,10 @@
 	}
 	
 	private RGB getStrokeColor(Diff diff) {
-		boolean selected= fCurrentDiff != null && fCurrentDiff.fParent == diff;
+		boolean selected= fCurrentDiff != null && fCurrentDiff.getParent() == diff;
 		
 		if (isThreeWay() && !isIgnoreAncestor()) {
-			switch (diff.fDirection) {
+			switch (diff.getKind()) {
 			case RangeDifference.RIGHT:
 				if (fLeftIsLocal)
 					return selected ? SELECTED_INCOMING : INCOMING;
@@ -4501,12 +3684,13 @@
 		if (part == null)
 			return null;
 		Point s = part.getSelectedRange();
+		char leg = getLeg(part);
 		for (;;) {
 			diff = null;
 			diff = internalGetNextDiff(down, deep, part, s);
-			if (diff != null && diff.fDirection == RangeDifference.ANCESTOR
+			if (diff != null && diff.getKind() == RangeDifference.ANCESTOR
 					&& !isAncestorVisible()) {
-				Position position = diff.getPosition(part);
+				Position position = diff.getPosition(leg);
 				s = new Point(position.getOffset(), position.getLength());
 				diff= null;
 				continue;
@@ -4517,10 +3701,10 @@
 	}
 	
 	private Diff internalGetNextDiff(boolean down, boolean deep, MergeSourceViewer part, Point s) {
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			if (down)
-				return findNext(part, fChangeDiffs, s.x, s.x+s.y, deep);
-			return findPrev(part, fChangeDiffs, s.x, s.x+s.y, deep);						
+				return findNext(part, s.x, s.x+s.y, deep);
+			return findPrev(part, s.x, s.x+s.y, deep);						
 		}
 		return null;
 	}
@@ -4533,12 +3717,7 @@
 	}
 
 	private Diff getWrappedDiff(Diff diff, boolean down) {
-		if (fChangeDiffs != null && fChangeDiffs.size() > 0) {
-			if (down)
-				return (Diff) fChangeDiffs.get(0);
-			return (Diff) fChangeDiffs.get(fChangeDiffs.size()-1);
-		}
-		return null;
+		return fMerger.getWrappedDiff(diff, down);
 	}
 	
 	/*
@@ -4558,7 +3737,7 @@
 			}
 			if (diff != null)
 				setCurrentDiff(diff, true, deep);
-			if (diff != null && diff.fDirection == RangeDifference.ANCESTOR
+			if (diff != null && diff.getKind() == RangeDifference.ANCESTOR
 					&& !isAncestorVisible())
 				continue;
 			break;
@@ -4662,83 +3841,16 @@
 	 * is returned.
 	 */
 	private Diff findDiff(MergeSourceViewer tp, int rangeStart, int rangeEnd) {
-		if (fChangeDiffs != null) {
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff diff= (Diff) e.next();
-				if (diff.overlaps(tp, rangeStart, rangeEnd))
-					return diff;
-			}
-		}
-		return null;
+		char contributor = getLeg(tp);
+		return fMerger.findDiff(contributor, rangeStart, rangeEnd);
 	}
 	
-	private static Diff findNext(MergeSourceViewer tp, List v, int start, int end, boolean deep) {
-		for (int i= 0; i < v.size(); i++) {
-			Diff diff= (Diff) v.get(i);
-			Position p= diff.getPosition(tp);
-			if (p != null) {
-				int startOffset= p.getOffset();
-				if (end < startOffset)  // <=
-					return diff;
-				if (deep && diff.fDiffs != null) {
-					Diff d= null;
-					int endOffset= startOffset + p.getLength();
-					if (start == startOffset && (end == endOffset || end == endOffset-1)) {
-						d= findNext(tp, diff.fDiffs, start-1, start-1, deep);
-					} else if (end < endOffset) {
-						d= findNext(tp, diff.fDiffs, start, end, deep);
-					}
-					if (d != null)
-						return d;
-				}
-			}
-		}
-		return null;
+	private Diff findNext(MergeSourceViewer tp, int start, int end, boolean deep) {
+		return fMerger.findNext(getLeg(tp), start, end, deep);
 	}
 	
-	private static Diff findPrev(MergeSourceViewer tp, List v, int start, int end, boolean deep) {
-		for (int i= v.size()-1; i >= 0; i--) {
-			Diff diff= (Diff) v.get(i);
-			Position p= diff.getPosition(tp);
-			if (p != null) {
-				int startOffset= p.getOffset();
-				int endOffset= startOffset + p.getLength();
-				if (start > endOffset) {
-					if (deep && diff.fDiffs != null) {
-						// If we are going deep, find the last change in the diff
-						return findPrev(tp, diff.fDiffs, end, end, deep);
-					}
-					return diff;
-				}
-				if (deep && diff.fDiffs != null) {
-					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 (isFirstDiff(tp, startOffset, diff.fDiffs)) {
-							return diff;
-						}
-						d= findPrev(tp, diff.fDiffs, start, end, deep);
-					}
-					if (d != null)
-						return d;
-				}
-			}
-		}
-		return null;
-	}
-		
-	private static boolean isFirstDiff(MergeSourceViewer tp, int startOffset,
-			ArrayList diffs) {
-		if (diffs.isEmpty())
-			return false;
-		Diff diff = (Diff)diffs.get(0);
-		Position p= diff.getPosition(tp);
-		return (p.getOffset() >= startOffset);
+	private Diff findPrev(MergeSourceViewer tp, int start, int end, boolean deep) {
+		return fMerger.findPrev(getLeg(tp), start, end, deep);
 	}
 
 	/*
@@ -4770,27 +3882,27 @@
 			// before we set fCurrentDiff we change the selection
 			// so that the paint code uses the old background colors
 			// otherwise selection isn't drawn correctly
-			if (d.fIsToken || !fHighlightTokenChanges || deep || !d.hasChildren()) {
+			if (d.isToken() || !fHighlightTokenChanges || deep || !d.hasChildren()) {
 				if (isThreeWay() && !isIgnoreAncestor())
-					fAncestor.setSelection(d.fAncestorPos);
-				fLeft.setSelection(d.fLeftPos);
-				fRight.setSelection(d.fRightPos);
+					fAncestor.setSelection(d.getPosition(ANCESTOR_CONTRIBUTOR));
+				fLeft.setSelection(d.getPosition(LEFT_CONTRIBUTOR));
+				fRight.setSelection(d.getPosition(RIGHT_CONTRIBUTOR));
 			} else {
 				if (isThreeWay() && !isIgnoreAncestor())
-					fAncestor.setSelection(new Position(d.fAncestorPos.offset, 0));
-				fLeft.setSelection(new Position(d.fLeftPos.offset, 0));
-				fRight.setSelection(new Position(d.fRightPos.offset, 0));
+					fAncestor.setSelection(new Position(d.getPosition(ANCESTOR_CONTRIBUTOR).offset, 0));
+				fLeft.setSelection(new Position(d.getPosition(LEFT_CONTRIBUTOR).offset, 0));
+				fRight.setSelection(new Position(d.getPosition(RIGHT_CONTRIBUTOR).offset, 0));
 			}
 			
 			// now switch diffs
 			fCurrentDiff= d;
-			revealDiff(d, d.fIsToken);
+			revealDiff(d, d.isToken());
 		} else {
 			fCurrentDiff= d;
 		}
 		
-		Diff d1= oldDiff != null ? oldDiff.fParent : null;
-		Diff d2= fCurrentDiff != null ? fCurrentDiff.fParent : null;
+		Diff d1= oldDiff != null ? oldDiff.getParent() : null;
+		Diff d2= fCurrentDiff != null ? fCurrentDiff.getParent() : null;
 		if (d1 != d2) {
 			updateDiffBackground(d1);
 			updateDiffBackground(d2);
@@ -4813,11 +3925,11 @@
 		if (smart) {
 			Point region= new Point(0, 0);
 			// find the starting line of the diff in all text widgets
-			int ls= fLeft.getLineRange(d.fLeftPos, region).x;
-			int rs= fRight.getLineRange(d.fRightPos, region).x;
+			int ls= fLeft.getLineRange(d.getPosition(LEFT_CONTRIBUTOR), region).x;
+			int rs= fRight.getLineRange(d.getPosition(RIGHT_CONTRIBUTOR), region).x;
 			
 			if (isThreeWay() && !isIgnoreAncestor()) {
-				int as= fAncestor.getLineRange(d.fAncestorPos, region).x;
+				int as= fAncestor.getLineRange(d.getPosition(ANCESTOR_CONTRIBUTOR), region).x;
 				if (as >= fAncestor.getTopIndex() && as <= fAncestor.getBottomIndex())
 					ancestorIsVisible= true;
 			}
@@ -4835,33 +3947,31 @@
 			
 			MergeSourceViewer allButThis= null;
 			if (leftIsVisible) {
-				avpos= lvpos= rvpos= realToVirtualPosition(fLeft, fLeft.getTopIndex());
+				avpos= lvpos= rvpos= realToVirtualPosition(LEFT_CONTRIBUTOR, fLeft.getTopIndex());
 				allButThis= fLeft;
 			} else if (rightIsVisible) {
-				avpos= lvpos= rvpos= realToVirtualPosition(fRight, fRight.getTopIndex());
+				avpos= lvpos= rvpos= realToVirtualPosition(RIGHT_CONTRIBUTOR, fRight.getTopIndex());
 				allButThis= fRight;
 			} else if (ancestorIsVisible) {
-				avpos= lvpos= rvpos= realToVirtualPosition(fAncestor, fAncestor.getTopIndex());
+				avpos= lvpos= rvpos= realToVirtualPosition(ANCESTOR_CONTRIBUTOR, fAncestor.getTopIndex());
 				allButThis= fAncestor;
 			} else {
-				if (fAllDiffs != null) {
-					int vpos= 0;
-					Iterator e= fAllDiffs.iterator();
-					for (int i= 0; e.hasNext(); i++) {
-						Diff diff= (Diff) e.next();
-						if (diff == d)
-							break;
-						if (fSynchronizedScrolling) {
-							vpos+= diff.getMaxDiffHeight();
-						} else {
-							avpos+= diff.getAncestorHeight();
-							lvpos+= diff.getLeftHeight();
-							rvpos+= diff.getRightHeight();
-						}
+				int vpos= 0;
+				for (Iterator iterator = fMerger.rangesIterator(); iterator
+						.hasNext();) {
+					Diff diff = (Diff) iterator.next();
+					if (diff == d)
+						break;
+					if (fSynchronizedScrolling) {
+						vpos+= diff.getMaxDiffHeight();
+					} else {
+						avpos+= diff.getAncestorHeight();
+						lvpos+= diff.getLeftHeight();
+						rvpos+= diff.getRightHeight();
 					}
-					if (fSynchronizedScrolling)
-						avpos= lvpos= rvpos= vpos;
 				}
+				if (fSynchronizedScrolling)
+					avpos= lvpos= rvpos= vpos;
 				int delta= fRight.getViewportLines()/4;
 				avpos-= delta;
 				if (avpos < 0)
@@ -4881,11 +3991,11 @@
 		}
 		
 		// horizontal scrolling
-		if (d.fIsToken) {
+		if (d.isToken()) {
 			// we only scroll horizontally for token diffs
-			reveal(fAncestor, d.fAncestorPos);
-			reveal(fLeft, d.fLeftPos);
-			reveal(fRight, d.fRightPos);
+			reveal(fAncestor, d.getPosition(ANCESTOR_CONTRIBUTOR));
+			reveal(fLeft, d.getPosition(LEFT_CONTRIBUTOR));
+			reveal(fRight, d.getPosition(RIGHT_CONTRIBUTOR));
 		} else {
 			// in all other cases we reset the horizontal offset
 			hscroll(fAncestor);
@@ -4916,14 +4026,13 @@
 	//--------------------------------------------------------------------------------
 	
 	void copyAllUnresolved(boolean leftToRight) {
-		if (fChangeDiffs != null && isThreeWay() && !isIgnoreAncestor()) {
+		if (fMerger.hasChanges() && isThreeWay() && !isIgnoreAncestor()) {
 			IRewriteTarget target= leftToRight ? fRight.getRewriteTarget() : fLeft.getRewriteTarget();
 			boolean compoundChangeStarted= false;
-			Iterator e= fChangeDiffs.iterator();
 			try {
-				while (e.hasNext()) {
-					Diff diff= (Diff) e.next();
-					switch (diff.fDirection) {
+				for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+					Diff diff = (Diff) iterator.next();
+					switch (diff.getKind()) {
 					case RangeDifference.LEFT:
 						if (leftToRight) {
 							if (!compoundChangeStarted) {
@@ -5029,66 +4138,15 @@
 		if (diff != null && !diff.isResolved()) {
 			if (!validateChange(!leftToRight)) 
 				return false;
-			Position fromPos= null;
-			Position toPos= null;
-			IDocument fromDoc= null;
-			IDocument toDoc= null;
-
 			if (leftToRight) {
 				fRight.setEnabled(true);
-				fromPos= diff.fLeftPos;
-				toPos= diff.fRightPos;
-				fromDoc= fLeft.getDocument();
-				toDoc= fRight.getDocument();
 			} else {
 				fLeft.setEnabled(true);
-				fromPos= diff.fRightPos;
-				toPos= diff.fLeftPos;
-				fromDoc= fRight.getDocument();
-				toDoc= fLeft.getDocument();
 			}
-			
-			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.fDirection) {
-					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);
-							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);
-			updateResolveStatus();
-			
-			return true;
+			boolean result = fMerger.copy(diff, leftToRight);
+			if (result)
+				updateResolveStatus();
+			return result;
 		}
 		return false;
 	}
@@ -5104,36 +4162,6 @@
 	}
 
 	//---- scrolling
-
-	/*
-	 * Calculates virtual height (in lines) of views by adding the maximum of corresponding diffs.
-	 */
-	private int getVirtualHeight() {
-		int h= 1;
-		if (fAllDiffs != null) {
-			Iterator e= fAllDiffs.iterator();
-			for (int i= 0; e.hasNext(); i++) {
-				Diff diff= (Diff) e.next();
-				h+= diff.getMaxDiffHeight();
-			}
-		}
-		return h;
-	}
-	
-	/*
-	 * Calculates height (in lines) of right view by adding the height of the right diffs.
-	 */
-	private int getRightHeight() {
-		int h= 1;
-		if (fAllDiffs != null) {
-			Iterator e= fAllDiffs.iterator();
-			for (int i= 0; e.hasNext(); i++) {
-				Diff diff= (Diff) e.next();
-				h+= diff.getRightHeight();
-			}
-		}
-		return h;
-	}
 	
 	/*
 	 * The height of the TextEditors in lines.
@@ -5160,35 +4188,10 @@
 	/*
 	 * Returns the virtual position for the given view position.
 	 */
-	private int realToVirtualPosition(MergeSourceViewer w, int vpos) {
-
-		if (! fSynchronizedScrolling || fAllDiffs == null)
+	private int realToVirtualPosition(char contributor, int vpos) {
+		if (! fSynchronizedScrolling)
 			return vpos;
-				
-		int viewPos= 0;		// real view position
-		int virtualPos= 0;	// virtual position
-		Point region= new Point(0, 0);
-		
-		Iterator e= fAllDiffs.iterator();
-		while (e.hasNext()) {
-			Diff diff= (Diff) e.next();
-			Position pos= diff.getPosition(w);
-			w.getLineRange(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;
+		return fMerger.realToVirtualPosition(contributor, vpos);
 	}
 		
 	private void scrollVertical(int avpos, int lvpos, int rvpos, MergeSourceViewer allBut) {
@@ -5196,7 +4199,7 @@
 		int s= 0;
 		
 		if (fSynchronizedScrolling) {
-			s= getVirtualHeight() - rvpos;
+			s= fMerger.getVirtualHeight() - rvpos;
 			int height= fRight.getViewportLines()/4;
 			if (s < 0)
 				s= 0;
@@ -5208,21 +4211,21 @@
 				
 		if (isThreeWay() && allBut != fAncestor) {
 			if (fSynchronizedScrolling || allBut == null) {
-				int y= virtualToRealPosition(fAncestor, avpos+s)-s;
+				int y= virtualToRealPosition(ANCESTOR_CONTRIBUTOR, avpos+s)-s;
 				fAncestor.vscroll(y);
 			}
 		}
 
 		if (allBut != fLeft) {
 			if (fSynchronizedScrolling || allBut == null) {
-				int y= virtualToRealPosition(fLeft, lvpos+s)-s;
+				int y= virtualToRealPosition(LEFT_CONTRIBUTOR, lvpos+s)-s;
 				fLeft.vscroll(y);
 			}
 		}
 
 		if (allBut != fRight) {
 			if (fSynchronizedScrolling || allBut == null) {
-				int y= virtualToRealPosition(fRight, rvpos+s)-s;
+				int y= virtualToRealPosition(RIGHT_CONTRIBUTOR, rvpos+s)-s;
 				fRight.vscroll(y);
 			}
 		}
@@ -5254,12 +4257,12 @@
 		int ix= w.getTopIndex();
 		int ix2= w.getDocumentRegionOffset();
 		
-		int viewPosition= realToVirtualPosition(w, ix-ix2);
+		int viewPosition= realToVirtualPosition(getLeg(w), ix-ix2);
 				
 		scrollVertical(viewPosition, viewPosition, viewPosition, w);	// scroll all but the given views
 		
 		if (fVScrollBar != null) {
-			int value= Math.max(0, Math.min(viewPosition, getVirtualHeight() - getViewportHeight()));
+			int value= Math.max(0, Math.min(viewPosition, fMerger.getVirtualHeight() - getViewportHeight()));
 			fVScrollBar.setSelection(value);
 			//refreshBirdEyeView();
 		}
@@ -5270,7 +4273,7 @@
 	private void updateVScrollBar() {
 		
 		if (Utilities.okToUse(fVScrollBar) && fSynchronizedScrolling) {
-			int virtualHeight= getVirtualHeight();
+			int virtualHeight= fMerger.getVirtualHeight();
 			int viewPortHeight= getViewportHeight();
 			int pageIncrement= viewPortHeight-1;
 			int thumb= (viewPortHeight > virtualHeight) ? virtualHeight : viewPortHeight;
@@ -5284,34 +4287,10 @@
 	/*
 	 * maps given virtual position into a real view position of this view.
 	 */
-	private int virtualToRealPosition(MergeSourceViewer part, int v) {
-			
-		if (! fSynchronizedScrolling || fAllDiffs == null)
+	private int virtualToRealPosition(char contributor, int v) {
+		if (! fSynchronizedScrolling)
 			return v;
-					
-		int virtualPos= 0;
-		int viewPos= 0;
-		Point region= new Point(0, 0);
-		
-		Iterator e= fAllDiffs.iterator();
-		while (e.hasNext()) {
-			Diff diff= (Diff) e.next();
-			Position pos= diff.getPosition(part);
-			int viewHeight= part.getLineRange(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;
+		return fMerger.virtualToRealPosition(contributor, v);
 	}
 	
 	/* (non-Javadoc)
@@ -5453,8 +4432,7 @@
 	private void resetDiffs() {
 		// clear stuff
 		fCurrentDiff= null;
-		fChangeDiffs= null;
-		fAllDiffs= null;
+		fMerger.reset();
 		resetPositions(fLeft.getDocument());
 		resetPositions(fRight.getDocument());
 		resetPositions(fAncestor.getDocument());
@@ -5492,4 +4470,36 @@
 			fFindReplaceTarget= new FindReplaceTarget();
 		return fFindReplaceTarget;
 	}
+	
+	/* package */ char getLeg(MergeSourceViewer w) {
+		if (w == fLeft)
+			return LEFT_CONTRIBUTOR;
+		if (w == fRight)
+			return RIGHT_CONTRIBUTOR;
+		if (w == fAncestor)
+			return ANCESTOR_CONTRIBUTOR;
+		return ANCESTOR_CONTRIBUTOR;
+	}
+	
+	public boolean isCurrentDiff(Diff diff) {
+		if (diff == null)
+			return false;
+		if (diff == fCurrentDiff)
+			return true;
+		if (fCurrentDiff != null && fCurrentDiff.getParent() == diff)
+			return true;
+		return false;
+	}
+	
+	public boolean isNavigationPossible() {
+		if (fCurrentDiff == null && fMerger.hasChanges())
+			return true;
+		else if (fMerger.changesCount() > 1)
+			return true;
+		else if (fCurrentDiff != null && fCurrentDiff.hasChildren())
+			return true;
+		else if (fCurrentDiff != null && fCurrentDiff.isToken())
+			return true;
+		return false;
+	}
 }
diff --git a/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java
new file mode 100644
index 0000000..6429090
--- /dev/null
+++ b/bundles/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java
@@ -0,0 +1,1338 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.merge;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.contentmergeviewer.ITokenComparator;
+import org.eclipse.compare.internal.*;
+import org.eclipse.compare.rangedifferencer.*;
+import org.eclipse.compare.structuremergeviewer.Differencer;
+import org.eclipse.core.runtime.*;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.text.*;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.progress.IProgressService;
+
+/**
+ * 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 fAllDiffs;
+	/** Subset of above: just real differences. */
+	private ArrayList fChangeDiffs;
+	
+	private final boolean fLeftIsLocal;
+
+	private IDocumentMergerInput fInput;
+	
+	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();
+		
+	}
+	
+	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 */
+		ArrayList 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+= Differencer.LEFT;
+				break;
+			case RangeDifference.LEFT:
+				code+= 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 e= fDiffs.iterator();
+				while (e.hasNext()) {
+					Diff d= (Diff) e.next();
+					if (!d.isResolved())
+						return false;
+				}
+				return true;
+			}
+			return fResolved;
+		}
+		
+//		private boolean isIncoming() {
+//			switch (fDirection) {
+//			case RangeDifference.RIGHT:
+//				if (fLeftIsLocal)
+//					return true;
+//				break;
+//			case RangeDifference.LEFT:
+//				if (!fLeftIsLocal)
+//					return true;
+//				break;
+//			}
+//			return false;
+//		}
+		
+		public boolean isIncomingOrConflicting() {
+			switch (fDirection) {
+			case RangeDifference.RIGHT:
+				if (fLeftIsLocal)
+					return true;
+				break;
+			case RangeDifference.LEFT:
+				if (!fLeftIsLocal)
+					return true;
+				break;
+			case RangeDifference.CONFLICT:
+				return true;
+			}
+			return false;
+		}
+		
+//		private boolean isUnresolvedIncoming() {
+//			if (fResolved)
+//				return false;
+//			return isIncoming();
+//		}
+		
+		public boolean isUnresolvedIncomingOrConflicting() {
+			if (fResolved)
+				return false;
+			return isIncomingOrConflicting();
+		}
+				
+		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) {
+			Position h= getPosition(contributor);
+			if (h != null) {
+				int ds= h.getOffset();
+				int de= ds + h.getLength();
+				if ((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 result = new ArrayList();
+				for (Iterator iterator = fDiffs.iterator(); iterator.hasNext();) {
+					Diff diff = (Diff) iterator.next();
+					if (diff.intersectsRegion(contributor, region)) {
+						result.add(diff);
+					}
+				}
+				return (Diff[]) 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 childIterator() {
+			if (fDiffs == null)
+				return new ArrayList().iterator();
+			return fDiffs.iterator();
+		}
+	}
+	
+	public DocumentMerger(IDocumentMergerInput input) {
+		this.fInput = input;
+		fLeftIsLocal= Utilities.getBoolean(getCompareConfiguration(), "LEFT_IS_LOCAL", false); //$NON-NLS-1$
+	}
+	
+	/**
+	 * 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();		
+		
+		DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace);
+		DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace);
+		DocLineComparator sancestor= null;
+		if (aDoc != null) {
+			sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
+			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= new IRunnableWithProgress() {
+			public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
+				monitor.beginTask("Computing Differences...", 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, "Too many differences found", ex.getTargetException()));
+		} catch (InterruptedException ex) {
+			// 
+		}
+		
+		ArrayList newAllDiffs = new ArrayList();
+		for (int i= 0; i < e.length; i++) {
+			RangeDifference es= e[i];
+			
+			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;
+	}
+
+	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();
+		
+		DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace);
+		DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace);
+		DocLineComparator sancestor= null;
+		if (aDoc != null)
+			sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
+			
+		final Object[] result= new Object[1];
+		final DocLineComparator sa= sancestor, sl= sleft, sr= sright;
+		IRunnableWithProgress runnable= new IRunnableWithProgress() {
+			public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
+				monitor.beginTask("Finding Differences...", 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();
+			}
+		};
+		IProgressService progressService= PlatformUI.getWorkbench().getProgressService();
+		
+		RangeDifference[] e= null;
+		try {
+			progressService.run(true, true, runnable);
+			e= (RangeDifference[]) result[0];
+		} catch (InvocationTargetException ex) {
+			throw new CoreException(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, "Too many differences found", ex.getTargetException()));
+		} catch (InterruptedException ex) {
+			// 
+		}
+					
+		if (e != null) {
+			for (int i= 0; i < e.length; i++) {
+				RangeDifference es= e[i];
+				
+				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 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 (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 (int i= 0; i < e.length; i++) {
+			RangeDifference es= e[i];
+			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 (Iterator iterator = fAllDiffs.iterator(); iterator.hasNext();) {
+			Diff diff = (Diff) iterator.next();
+			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 e= fAllDiffs.iterator();
+		while (e.hasNext()) {
+			Diff 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 e= fAllDiffs.iterator();
+		while (e.hasNext()) {
+			Diff 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 e= fAllDiffs.iterator();
+			for (int i= 0; e.hasNext(); i++) {
+				Diff 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 e= fAllDiffs.iterator();
+			for (int i= 0; e.hasNext(); i++) {
+				Diff 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 intersectingDiffs = new ArrayList();
+		for (Iterator iterator = fChangeDiffs.iterator(); iterator.hasNext();) {
+			Diff diff = (Diff) iterator.next();
+			Diff[] changeDiffs = diff.getChangeDiffs(contributor, region);
+			for (int i = 0; i < changeDiffs.length; i++) {
+				Diff changeDiff = changeDiffs[i];
+				intersectingDiffs.add(changeDiff);
+			}
+		}
+		return (Diff[]) 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 e= fAllDiffs.iterator();
+			for (int i= 0; e.hasNext(); i++) {
+				Diff 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 changesIterator() {
+		if (fChangeDiffs == null)
+			return new ArrayList().iterator();
+		return fChangeDiffs.iterator();
+	}
+	
+	public Iterator rangesIterator() {
+		if (fAllDiffs == null)
+			return new ArrayList().iterator();
+		return fAllDiffs.iterator();
+	}
+	
+	public boolean isFirstChildDiff(char contributor, int startOffset,
+			Diff diff) {
+		if (!diff.hasChildren())
+			return false;
+		Diff d = (Diff)diff.fDiffs.get(0);
+		Position p= d.getPosition(contributor);
+		return (p.getOffset() >= startOffset);
+	}
+	
+	public Diff getWrappedDiff(Diff diff, boolean down) {
+		if (fChangeDiffs != null && fChangeDiffs.size() > 0) {
+			if (down)
+				return (Diff) fChangeDiffs.get(0);
+			return (Diff) 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 && !diff.isResolved()) {
+			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);
+							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 iterator = changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
+				if (diff.isDeleted() || diff.getKind() == RangeDifference.NOCHANGE)
+				    continue;
+				if (diff.overlaps(contributor, rangeStart, rangeEnd))
+					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 v, int start, int end, boolean deep) {
+		if (v == null)
+			return null;
+		for (int i= 0; i < v.size(); i++) {
+			Diff diff= (Diff) v.get(i);
+			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 v, int start, int end, boolean deep) {
+		if (v == null)
+			return null;
+		for (int i= v.size()-1; i >= 0; i--) {
+			Diff 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, startOffset, diff)) {
+							return diff;
+						}
+						d= findPrev(contributor, diff.fDiffs, start, end, deep);
+					}
+					if (d != null)
+						return d;
+				}
+			}
+		}
+		return null;
+	}
+	
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java
index 9cb18d1..659dee7 100644
--- a/bundles/org.eclipse.compare/plugins/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare/compare/org/eclipse/compare/contentmergeviewer/TextMergeViewer.java
@@ -19,12 +19,14 @@
 import java.io.UnsupportedEncodingException;
 import java.lang.reflect.InvocationTargetException;
 import java.util.*;
-import java.util.List;
 
 import org.eclipse.compare.*;
 import org.eclipse.compare.internal.*;
+import org.eclipse.compare.internal.merge.DocumentMerger;
+import org.eclipse.compare.internal.merge.DocumentMerger.Diff;
+import org.eclipse.compare.internal.merge.DocumentMerger.IDocumentMergerInput;
 import org.eclipse.compare.patch.IHunk;
-import org.eclipse.compare.rangedifferencer.*;
+import org.eclipse.compare.rangedifferencer.RangeDifference;
 import org.eclipse.compare.structuremergeviewer.*;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.*;
@@ -32,7 +34,6 @@
 import org.eclipse.jface.action.*;
 import org.eclipse.jface.dialogs.ErrorDialog;
 import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.resource.ColorRegistry;
 import org.eclipse.jface.resource.JFaceResources;
@@ -54,7 +55,6 @@
 import org.eclipse.swt.widgets.*;
 import org.eclipse.ui.*;
 import org.eclipse.ui.actions.ActionFactory;
-import org.eclipse.ui.progress.IProgressService;
 import org.eclipse.ui.texteditor.*;
 
 import com.ibm.icu.text.MessageFormat;
@@ -161,13 +161,9 @@
 	private static final int BIRDS_EYE_VIEW_INSET= 2;
 	/** */
 	private static final int RESOLVE_SIZE= 5;
-	/** if true copying conflicts from one side to other concatenates both sides */
-	private static final boolean APPEND_CONFLICT= true;
 
 	/** line width of change borders */
 	private static final int LW= 1;
-	/** Selects between smartTokenDiff and mergingTokenDiff */
-	private static final boolean USE_MERGING_TOKEN_DIFF= false;
 		
 	// determines whether a change between left and right is considered incoming or outgoing
 	private boolean fLeftIsLocal;
@@ -202,13 +198,6 @@
 	private IPreferenceStore fPreferenceStore;
 	private IPropertyChangeListener fPreferenceChangeListener;
 	
-	/** All diffs for calculating scrolling position (includes line ranges without changes) */
-	private ArrayList fAllDiffs;
-	/** Subset of above: just real differences. */
-	private ArrayList fChangeDiffs;
-	/** The current diff */
-	private Diff fCurrentDiff;
-	
 	private HashMap fNewAncestorRanges= new HashMap();
 	private HashMap fNewLeftRanges= new HashMap();
 	private HashMap fNewRightRanges= new HashMap();
@@ -291,6 +280,9 @@
 	private TextEditorPropertyAction toggleLineNumbersAction;
 	private IFindReplaceTarget fFindReplaceTarget;
 	private ChangePropertyAction fIgnoreWhitespace;
+	private DocumentMerger fMerger;
+	/** The current diff */
+	private Diff fCurrentDiff;
 
 	private final class InternalOutlineViewerCreator extends OutlineViewerCreator implements ISelectionChangedListener {
 		public Viewer findStructureViewer(Viewer oldViewer,
@@ -340,16 +332,16 @@
 		}
 		
 	    private Diff findDiff(Position p, boolean left) {
-			for (Iterator iterator = fAllDiffs.iterator(); iterator.hasNext();) {
+			for (Iterator iterator = fMerger.rangesIterator(); iterator.hasNext();) {
 				Diff diff = (Diff) iterator.next();
 				Position diffPos;
 				if (left) {
-					diffPos = diff.fLeftPos;
+					diffPos = diff.getPosition(LEFT_CONTRIBUTOR);
 				} else {
-					diffPos = diff.fRightPos;
+					diffPos = diff.getPosition(RIGHT_CONTRIBUTOR);
 				}
 				// If the element falls within a diff, highlight that diff
-				if (diffPos.offset + diffPos.length >= p.offset && diff.fDirection != RangeDifference.NOCHANGE)
+				if (diffPos.offset + diffPos.length >= p.offset && diff.getKind() != RangeDifference.NOCHANGE)
 					return diff;
 				// Otherwise, highlight the first diff after the elements position
 				if (diffPos.offset >= p.offset)
@@ -1028,276 +1020,36 @@
 		}
 	}
 	
-	/*
-	 * A Diff represents synchronized character ranges in two or three Documents.
-	 * The MergeTextViewer uses Diffs to find differences in line and token ranges.
-	 */
-	/* package */ 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 */
-		ArrayList fDiffs;
-		boolean fIsWhitespace= false;
+	private class ChangeHighlighter implements ITextPresentationListener {
 
-		/*
-		 * Create Diff from two ranges and an optional parent diff.
+		private final MergeSourceViewer viewer;
+
+		public ChangeHighlighter(MergeSourceViewer viewer) {
+			this.viewer = viewer;
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation)
 		 */
-		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);
-		}
-		
-		Position getPosition(char type) {
-			switch (type) {
-			case ANCESTOR_CONTRIBUTOR:
-				return fAncestorPos;
-			case LEFT_CONTRIBUTOR:
-				return fLeftPos;
-			case RIGHT_CONTRIBUTOR:
-				return fRightPos;
+		public void applyTextPresentation(TextPresentation textPresentation) {
+			if (!fHighlightTokenChanges)
+				return;
+			IRegion region= textPresentation.getExtent();
+			Diff[] changeDiffs = fMerger.getChangeDiffs(getLeg(viewer), region);
+			for (int i = 0; i < changeDiffs.length; i++) {
+				Diff diff = changeDiffs[i];
+				StyleRange range = getStyleRange(diff, region);
+				if (range != null)
+					textPresentation.mergeStyleRange(range);
 			}
-			return null;
-		}
-		
-		boolean isInRange(char type, int pos) {
-			Position p= getPosition(type);
-			return (pos >= p.offset) && (pos < (p.offset+p.length));
-		}
-		
-		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; 
-		}
-		
-		Image getImage() {
-			int code= Differencer.CHANGE;
-			switch (fDirection) {
-			case RangeDifference.RIGHT:
-				code+= Differencer.LEFT;
-				break;
-			case RangeDifference.LEFT:
-				code+= 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);
-		}
-		
-		boolean isDeleted() {
-			if (fAncestorPos != null && fAncestorPos.isDeleted())
-				return true;
-			return fLeftPos.isDeleted() || fRightPos.isDeleted();
-		}
-		
-		void setResolved(boolean r) {
-			fResolved= r;
-			if (r)
-				fDiffs= null;
-		}
-
-		boolean isResolved() {
-			if (!fResolved && fDiffs != null) {
-				Iterator e= fDiffs.iterator();
-				while (e.hasNext()) {
-					Diff d= (Diff) e.next();
-					if (!d.isResolved())
-						return false;
-				}
-				return true;
-			}
-			return fResolved;
-		}
-		
-//		private boolean isIncoming() {
-//			switch (fDirection) {
-//			case RangeDifference.RIGHT:
-//				if (fLeftIsLocal)
-//					return true;
-//				break;
-//			case RangeDifference.LEFT:
-//				if (!fLeftIsLocal)
-//					return true;
-//				break;
-//			}
-//			return false;
-//		}
-		
-		private boolean isIncomingOrConflicting() {
-			switch (fDirection) {
-			case RangeDifference.RIGHT:
-				if (fLeftIsLocal)
-					return true;
-				break;
-			case RangeDifference.LEFT:
-				if (!fLeftIsLocal)
-					return true;
-				break;
-			case RangeDifference.CONFLICT:
-				return true;
-			}
-			return false;
-		}
-		
-//		private boolean isUnresolvedIncoming() {
-//			if (fResolved)
-//				return false;
-//			return isIncoming();
-//		}
-		
-		private boolean isUnresolvedIncomingOrConflicting() {
-			if (fResolved)
-				return false;
-			return isIncomingOrConflicting();
-		}
-				
-		Position getPosition(MergeSourceViewer w) {
-			if (w == fLeft)
-				return fLeftPos;
-			if (w == fRight)
-				return fRightPos;
-			if (w == fAncestor)
-				return fAncestorPos;
-			return null;
-		}
-		
-		/*
-		 * Returns true if given character range overlaps with this Diff.
-		 */
-		boolean overlaps(MergeSourceViewer w, int start, int end) {
-			Position h= getPosition(w);
-			if (h != null) {
-				int ds= h.getOffset();
-				int de= ds + h.getLength();
-				if ((start < de) && (end >= ds))
-					return true;	
-			}
-			return false;
-		}
-				
-		int getMaxDiffHeight() {
-			Point region= new Point(0, 0);
-			int h= fLeft.getLineRange(fLeftPos, region).y;
-			if (isThreeWay())
-				h= Math.max(h, fAncestor.getLineRange(fAncestorPos, region).y);
-			return Math.max(h, fRight.getLineRange(fRightPos, region).y);
-		}
-		
-		int getAncestorHeight() {
-			Point region= new Point(0, 0);			
-			return fAncestor.getLineRange(fAncestorPos, region).y;
-		}
-
-		int getLeftHeight() {
-			Point region= new Point(0, 0);			
-			return fLeft.getLineRange(fLeftPos, region).y;
-		}
-
-		int getRightHeight() {
-			Point region= new Point(0, 0);			
-			return fRight.getLineRange(fRightPos, region).y;
-		}
-
-		public Diff[] getChangeDiffs(MergeSourceViewer viewer, IRegion region) {
-			if (fDiffs != null && intersectsRegion(viewer, region)) {
-				List result = new ArrayList();
-				for (Iterator iterator = fDiffs.iterator(); iterator.hasNext();) {
-					Diff diff = (Diff) iterator.next();
-					if (diff.intersectsRegion(viewer, region)) {
-						result.add(diff);
-					}
-				}
-				return (Diff[]) result.toArray(new Diff[result.size()]);
-			}
-			return new Diff[0];
-		}
-
-		private boolean intersectsRegion(MergeSourceViewer viewer,
-				IRegion region) {
-			Position p = getPosition(viewer);
-			if (p != null)
-				return p.overlapsWith(region.getOffset(), region.getLength());
-			return false;
-		}
-
-		public StyleRange getStyleRange(MergeSourceViewer viewer, IRegion region) {
+		private StyleRange getStyleRange(Diff diff, IRegion region) {
 			//Color cText = getColor(null, getTextColor());
-			Color cTextFill = getColor(null, getTextFillColor());
+			Color cTextFill = getColor(null, getTextFillColor(diff));
 			if (cTextFill == null)
 				return null;
-			Position p = getPosition(viewer);
+			Position p = diff.getPosition(getLeg(viewer));
 			int start = p.getOffset();
 			int length = p.getLength();
 			// Don't start before the region
@@ -1316,9 +1068,9 @@
 			return new StyleRange(start, length, null, cTextFill);
 		}
 		
-		private RGB getTextFillColor() {
+		private RGB getTextFillColor(Diff diff) {
 			if (isThreeWay() && !isIgnoreAncestor()) {
-				switch (fDirection) {
+				switch (diff.getKind()) {
 				case RangeDifference.RIGHT:
 					if (fLeftIsLocal)
 						return INCOMING_TEXT_FILL;
@@ -1336,54 +1088,6 @@
 			}
 			return OUTGOING_TEXT_FILL;
 		}
-
-		public boolean hasChildren() {
-			return fDiffs != null && !fDiffs.isEmpty();
-		}
-	}
-	
-	private class ChangeHighlighter implements ITextPresentationListener {
-
-		private final MergeSourceViewer viewer;
-
-		public ChangeHighlighter(MergeSourceViewer viewer) {
-			this.viewer = viewer;
-		}
-
-		/* (non-Javadoc)
-		 * @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation)
-		 */
-		public void applyTextPresentation(TextPresentation textPresentation) {
-			if (!fHighlightTokenChanges)
-				return;
-			IRegion region= textPresentation.getExtent();
-			Diff[] changeDiffs = getChangeDiffs(region);
-			for (int i = 0; i < changeDiffs.length; i++) {
-				Diff diff = changeDiffs[i];
-				StyleRange range = getStyleRange(diff, region);
-				if (range != null)
-					textPresentation.mergeStyleRange(range);
-			}
-		}
-
-		private StyleRange getStyleRange(Diff diff, IRegion region) {
-			return diff.getStyleRange(viewer, region);
-		}
-
-		private Diff[] getChangeDiffs(IRegion region) {
-			if (fChangeDiffs == null)
-				return new Diff[0];
-			List intersectingDiffs = new ArrayList();
-			for (Iterator iterator = fChangeDiffs.iterator(); iterator.hasNext();) {
-				Diff diff = (Diff) iterator.next();
-				Diff[] changeDiffs = diff.getChangeDiffs(viewer, region);
-				for (int i = 0; i < changeDiffs.length; i++) {
-					Diff changeDiff = changeDiffs[i];
-					intersectingDiffs.add(changeDiff);
-				}
-			}
-			return (Diff[]) intersectingDiffs.toArray(new Diff[intersectingDiffs.size()]);
-		}
 		
 	}
 	
@@ -1438,6 +1142,58 @@
 	public TextMergeViewer(Composite parent, int style, CompareConfiguration configuration) {
 		super(style, ResourceBundle.getBundle(BUNDLE_NAME), configuration);
 		
+		fMerger = new DocumentMerger(new IDocumentMergerInput() {
+			public ITokenComparator createTokenComparator(String line) {
+				return TextMergeViewer.this.createTokenComparator(line);
+			}
+			public CompareConfiguration getCompareConfiguration() {
+				return TextMergeViewer.this.getCompareConfiguration();
+			}
+			public IDocument getDocument(char contributor) {
+				switch (contributor) {
+				case LEFT_CONTRIBUTOR:
+					return fLeft.getDocument();
+				case RIGHT_CONTRIBUTOR:
+					return fRight.getDocument();
+				case ANCESTOR_CONTRIBUTOR:
+					return fAncestor.getDocument();
+				}
+				return null;
+			}
+			public int getHunkStart() {
+				return TextMergeViewer.this.getHunkStart();
+			}
+			public Position getRegion(char contributor) {
+				switch (contributor) {
+				case LEFT_CONTRIBUTOR:
+					return fLeft.getRegion();
+				case RIGHT_CONTRIBUTOR:
+					return fRight.getRegion();
+				case ANCESTOR_CONTRIBUTOR:
+					return fAncestor.getRegion();
+				}
+				return null;
+			}
+			public boolean isHunkOnLeft() {
+				ITypedElement left = ((ICompareInput)getInput()).getRight();
+				return left != null && Utilities.getAdapter(left, IHunk.class) != null;
+			}
+			public boolean isIgnoreAncestor() {
+				return TextMergeViewer.this.isIgnoreAncestor();
+			}
+			public boolean isPatchHunk() {
+				return TextMergeViewer.this.isPatchHunk();
+			}
+
+			public boolean isShowPseudoConflicts() {
+				return fShowPseudoConflicts;
+			}
+			public boolean isThreeWay() {
+				return TextMergeViewer.this.isThreeWay();
+			}
+			
+		});
+		
 		int inheritedStyle= parent.getStyle();
 		if ((inheritedStyle & SWT.LEFT_TO_RIGHT) != 0)
 			fInheritedDirection= SWT.LEFT_TO_RIGHT;
@@ -1952,7 +1708,7 @@
 				public void mouseMove(MouseEvent e) {
 					Cursor cursor= null;
 					Diff diff= handlemouseInBirdsEyeView(fBirdsEyeCanvas, e.y);
-					if (diff != null && diff.fDirection != RangeDifference.NOCHANGE)
+					if (diff != null && diff.getKind() != RangeDifference.NOCHANGE)
 						cursor= fBirdsEyeCursor;
 					if (fLastCursor != cursor) {
 						fBirdsEyeCanvas.setCursor(cursor);
@@ -1993,7 +1749,7 @@
 	}
 
 	private void setCurrentDiff2(Diff diff, boolean reveal) {
-		if (diff != null && diff.fDirection != RangeDifference.NOCHANGE) {
+		if (diff != null && diff.getKind() != RangeDifference.NOCHANGE) {
 			//fCurrentDiff= null;
 			setCurrentDiff(diff, reveal);
 		}
@@ -2007,20 +1763,20 @@
 		if (! fHighlightRanges)
 			return null;
 
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			int shift= tp.getVerticalScrollOffset() + (2-LW);
 
 			Point region= new Point(0, 0);
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff diff= (Diff) e.next();
+			char leg = getLeg(tp);
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
 				if (diff.isDeleted())
 					continue;
 
 				if (fShowCurrentOnly2 && !isCurrentDiff(diff))
 					continue;
 
-				tp.getLineRange(diff.getPosition(tp), region);
+				tp.getLineRange(diff.getPosition(leg), region);
 				int y= (region.x * lineHeight) + shift;
 				int h= region.y * lineHeight;
 
@@ -2050,26 +1806,25 @@
 		if (! fHighlightRanges)
 			return null;
 
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			int lshift= fLeft.getVerticalScrollOffset();
 			int rshift= fRight.getVerticalScrollOffset();
 
 			Point region= new Point(0, 0);
 
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff diff= (Diff) e.next();
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
 				if (diff.isDeleted())
 					continue;
 
 				if (fShowCurrentOnly2 && !isCurrentDiff(diff))
 					continue;
 
-				fLeft.getLineRange(diff.fLeftPos, region);
+				fLeft.getLineRange(diff.getPosition(LEFT_CONTRIBUTOR), region);
 				int ly= (region.x * lineHeight) + lshift;
 				int lh= region.y * lineHeight;
 
-				fRight.getLineRange(diff.fRightPos, region);
+				fRight.getLineRange(diff.getPosition(RIGHT_CONTRIBUTOR), region);
 				int ry= (region.x * lineHeight) + rshift;
 				int rh= region.y * lineHeight;
 
@@ -2096,35 +1851,7 @@
 	}
 
 	private Diff handlemouseInBirdsEyeView(Canvas canvas, int my) {
-		int yy, hh;
-		
-		Point size= canvas.getSize();
-		
-		int virtualHeight= fSynchronizedScrolling ? getVirtualHeight() : getRightHeight();		
-		if (virtualHeight < getViewportHeight())
-			return null;
-		
-		int y= 0;
-		if (fAllDiffs != null) {
-			Iterator e= fAllDiffs.iterator();
-			for (int i= 0; e.hasNext(); i++) {
-				Diff diff= (Diff) e.next();
-				int h= fSynchronizedScrolling ? diff.getMaxDiffHeight()
-											  : diff.getRightHeight();
-				if (useChange(diff.fDirection) && !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;
+		return fMerger.findDiff(getViewportHeight(), fSynchronizedScrolling, canvas.getSize(), my);
 	}
 	
 	private void paintBirdsEyeView(Canvas canvas, GC gc) {
@@ -2135,54 +1862,50 @@
 		
 		Point size= canvas.getSize();
 		
-		int virtualHeight= fSynchronizedScrolling ? getVirtualHeight() : getRightHeight();		
+		int virtualHeight= fSynchronizedScrolling ? fMerger.getVirtualHeight() : fMerger.getRightHeight();		
 		if (virtualHeight < getViewportHeight())
 			return;
 				
 		Display display= canvas.getDisplay();
 		int y= 0;
-		if (fAllDiffs != null) {
-			Iterator e= fAllDiffs.iterator();
-			for (int i= 0; e.hasNext(); i++) {
-				Diff diff= (Diff) e.next();
-				int h= fSynchronizedScrolling ? diff.getMaxDiffHeight()
-											  : diff.getRightHeight();
-								
-				if (useChange(diff.fDirection) && !diff.fIsWhitespace) {
-					
-					yy= (y*size.y)/virtualHeight;
-					hh= (h*size.y)/virtualHeight;
-					if (hh < 3)
-						hh= 3;
-					
-					c= getColor(display, getFillColor(diff));
-					if (c != null) {
-						gc.setBackground(c);
-						gc.fillRectangle(BIRDS_EYE_VIEW_INSET, yy, size.x-(2*BIRDS_EYE_VIEW_INSET),hh);
-					}
-					c= getColor(display, getStrokeColor(diff));
-					if (c != null) {
-						gc.setForeground(c);
-						r.x= BIRDS_EYE_VIEW_INSET;
-						r.y= yy;
-						r.width= size.x-(2*BIRDS_EYE_VIEW_INSET)-1;
-						r.height= hh;
-						if (diff == fCurrentDiff ||
-								(fCurrentDiff != null && diff == fCurrentDiff.fParent)) {
-							gc.setLineWidth(2);
-							r.x++;
-							r.y++;
-							r.width--;
-							r.height--;
-						} else {
-							gc.setLineWidth(0 /* 1 */);
-						}
-						gc.drawRectangle(r);
-					}
-				}
+		for (Iterator iterator = fMerger.rangesIterator(); iterator.hasNext();) {
+			Diff diff = (Diff) iterator.next();
+			int h= fSynchronizedScrolling ? diff.getMaxDiffHeight()
+										  : diff.getRightHeight();
+							
+			if (fMerger.useChange(diff)) {
 				
-				y+= h;
+				yy= (y*size.y)/virtualHeight;
+				hh= (h*size.y)/virtualHeight;
+				if (hh < 3)
+					hh= 3;
+				
+				c= getColor(display, getFillColor(diff));
+				if (c != null) {
+					gc.setBackground(c);
+					gc.fillRectangle(BIRDS_EYE_VIEW_INSET, yy, size.x-(2*BIRDS_EYE_VIEW_INSET),hh);
+				}
+				c= getColor(display, getStrokeColor(diff));
+				if (c != null) {
+					gc.setForeground(c);
+					r.x= BIRDS_EYE_VIEW_INSET;
+					r.y= yy;
+					r.width= size.x-(2*BIRDS_EYE_VIEW_INSET)-1;
+					r.height= hh;
+					if (isCurrentDiff(diff)) {
+						gc.setLineWidth(2);
+						r.x++;
+						r.y++;
+						r.width--;
+						r.height--;
+					} else {
+						gc.setLineWidth(0 /* 1 */);
+					}
+					gc.drawRectangle(r);
+				}
 			}
+			
+			y+= h;
 		}
 	}
 	
@@ -2257,7 +1980,7 @@
 								setCurrentDiff(fButtonDiff, false);
 								copy(fCurrentDiff, 
 										fCenterButton.getText().equals(COPY_LEFT_TO_RIGHT_INDICATOR), 
-										fCurrentDiff.fDirection != RangeDifference.CONFLICT);
+										fCurrentDiff.getKind() != RangeDifference.CONFLICT);
 							}
 						}
 					}
@@ -2651,9 +2374,9 @@
 					Diff selectDiff= null;
 					if (FIX_47640) {
 						if (leftRange != null)
-						    selectDiff= findDiff(LEFT_CONTRIBUTOR, leftRange);
+						    selectDiff= fMerger.findDiff(LEFT_CONTRIBUTOR, leftRange);
 						else if (rightRange != null)
-						    selectDiff= findDiff(RIGHT_CONTRIBUTOR, rightRange);
+						    selectDiff= fMerger.findDiff(RIGHT_CONTRIBUTOR, rightRange);
 					}
 					if (selectDiff != null)
 						setCurrentDiff(selectDiff, true);
@@ -2673,37 +2396,12 @@
 		return new ContributorInfo(this, element, leg);
 	}
 	
-	private Diff findDiff(char c, Position range) {
-		
-		MergeSourceViewer v;
-		int start= range.getOffset();
-		int end= start + range.getLength();
-		if (c == LEFT_CONTRIBUTOR)
-			v= fLeft;
-		else if (c == RIGHT_CONTRIBUTOR)
-			v= fRight;
-		else
-			return null;
-		
-		if (fChangeDiffs != null) {
-			Iterator iter= fChangeDiffs.iterator();
-			while (iter.hasNext()) {
-				Diff diff= (Diff) iter.next();
-				if (diff.isDeleted() || diff.fDirection == RangeDifference.NOCHANGE)
-				    continue;
-				if (diff.overlaps(v, start, end))
-					return diff;
-			}
-		}
-		return null;
-	}
-	
 	private void updateDiffBackground(Diff diff) {
 		
 		if (! fHighlightRanges)
 			return;
 		
-		if (diff == null || diff.fIsToken)
+		if (diff == null || diff.isToken())
 			return;
 			
 		if (fShowCurrentOnly && !isCurrentDiff(diff))
@@ -2714,36 +2412,25 @@
 			return;
 			
 		if (isThreeWay())
-			fAncestor.setLineBackground(diff.fAncestorPos, c);
-		fLeft.setLineBackground(diff.fLeftPos, c);
-		fRight.setLineBackground(diff.fRightPos, c);
+			fAncestor.setLineBackground(diff.getPosition(ANCESTOR_CONTRIBUTOR), c);
+		fLeft.setLineBackground(diff.getPosition(LEFT_CONTRIBUTOR), c);
+		fRight.setLineBackground(diff.getPosition(RIGHT_CONTRIBUTOR), c);
 	}
 	
 	private void updateAllDiffBackgrounds(Display display) {
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			boolean threeWay= isThreeWay();
-			Iterator iter= fChangeDiffs.iterator();
-			while (iter.hasNext()) {
-				Diff diff= (Diff) iter.next();
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
 				Color c= getColor(display, getFillColor(diff));
 				if (threeWay)
-					fAncestor.setLineBackground(diff.fAncestorPos, c);
-				fLeft.setLineBackground(diff.fLeftPos, c);
-				fRight.setLineBackground(diff.fRightPos, c);
+					fAncestor.setLineBackground(diff.getPosition(ANCESTOR_CONTRIBUTOR), c);
+				fLeft.setLineBackground(diff.getPosition(LEFT_CONTRIBUTOR), c);
+				fRight.setLineBackground(diff.getPosition(RIGHT_CONTRIBUTOR), c);
 			}
 		}
 	}
 	
-	boolean isCurrentDiff(Diff diff) {
-		if (diff == null)
-			return false;
-		if (diff == fCurrentDiff)
-			return true;
-		if (fCurrentDiff != null && fCurrentDiff.fParent == diff)
-			return true;
-		return false;
-	}
-	
 	/*
 	 * Called whenever one of the documents changes.
 	 * Sets the dirty state of this viewer and updates the lines.
@@ -2810,22 +2497,7 @@
 			IDocumentRange dr= (IDocumentRange) other;
 			Position p= dr.getRange();	
 			Diff diff= findDiff(otherType, p.offset);
-			if (diff != null) {
-				switch (type) {
-				case ANCESTOR_CONTRIBUTOR:
-					if (diff.fAncestorPos != null)
-						return diff.fAncestorPos.offset;
-					break;
-				case LEFT_CONTRIBUTOR:
-					if (diff.fLeftPos != null)
-						return diff.fLeftPos.offset;
-					break;
-				case RIGHT_CONTRIBUTOR:
-					if (diff.fRightPos != null)
-						return diff.fRightPos.offset;
-					break;
-				}
-			}
+			return fMerger.findInsertionPoint(diff, type);
 		}
 		return 0;
 	}
@@ -3059,224 +2731,53 @@
 	}
 	
 	//---- the differencing
-
-	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);
-	}
 	
 	/**
 	 * Perform a two level 2- or 3-way diff.
 	 * The first level is based on line comparison, the second level on token comparison.
 	 */
 	private void doDiff() {
-						
-		ArrayList newAllDiffs = new ArrayList();
-		fChangeDiffs= new ArrayList();
-		fCurrentDiff= null;
-		
-		IDocument aDoc= null;
 		IDocument lDoc= fLeft.getDocument();
 		IDocument rDoc= fRight.getDocument();
 		if (lDoc == null || rDoc == null)
 			return;
-			
-		Position aRegion= null;
-		Position lRegion= fLeft.getRegion();
-		Position rRegion= fRight.getRegion();
-				
-		boolean threeWay= isThreeWay();
-		
-		if (threeWay && !isIgnoreAncestor()) {
-			aDoc= fAncestor.getDocument();
-			aRegion= fAncestor.getRegion();
-		}
-		
-		resetPositions(lDoc);
-		resetPositions(rDoc);
-		resetPositions(aDoc);
-		
 		fAncestor.resetLineBackground();
 		fLeft.resetLineBackground();
 		fRight.resetLineBackground();
 		
-		boolean ignoreWhiteSpace= Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false);		
-		
-		DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace);
-		DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace);
-		DocLineComparator sancestor= null;
-		boolean isRight = true;
-		if (aDoc != null) {
-			sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
-			if (isPatchHunk()) {
-				ITypedElement right = ((ICompareInput)getInput()).getRight();
-				isRight = right != null && Utilities.getAdapter(right, IHunk.class) != null;
-				if (isRight) {
-					sleft= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
-				} else {
-					sright= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
-				}
-			}
-		}
-			
-		if (!fSubDoc && rRegion != null && lRegion != null) {
-			// we have to add a diff for the ignored lines
-			
-			int astart= 0;
-			int as= 0;
-			if (aRegion != null) {
-				astart= aRegion.getOffset();
-				as= Math.max(0, astart-1);
-			}
-			int ys= Math.max(0, lRegion.getOffset()-1);
-			int ms= Math.max(0, rRegion.getOffset()-1);
-			
-			if (as > 0 || ys > 0 || ms > 0) {
-				Diff diff= new Diff(null, RangeDifference.NOCHANGE,
-					aDoc, aRegion, 0, astart,
-					lDoc, lRegion, 0, lRegion.getOffset(),
-					rDoc, rRegion, 0, rRegion.getOffset());
-				newAllDiffs.add(diff);
-			}
-		}
-		
-		final ResourceBundle bundle= getResourceBundle();
-			
-		final Object[] result= new Object[1];
-		final DocLineComparator sa= sancestor, sl= sleft, sr= sright;
-		IRunnableWithProgress runnable= new IRunnableWithProgress() {
-			public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
-				String progressTitle= Utilities.getString(bundle, "compareProgressTask.title"); //$NON-NLS-1$
-				monitor.beginTask(progressTitle, 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;
+		fCurrentDiff= null;
 		try {
-			getCompareConfiguration().getContainer().run(true, true, runnable);
-			e= (RangeDifference[]) result[0];
-		} catch (InvocationTargetException ex) {
-			String title= Utilities.getString(bundle, "tooComplexError.title"); //$NON-NLS-1$
-			String format= Utilities.getString(bundle, "tooComplexError.format"); //$NON-NLS-1$
+			fMerger.doDiff();
+		} catch (CoreException e) {
+			CompareUIPlugin.log(e.getStatus());
+			String title= Utilities.getString(getResourceBundle(), "tooComplexError.title"); //$NON-NLS-1$
+			String format= Utilities.getString(getResourceBundle(), "tooComplexError.format"); //$NON-NLS-1$
 			String msg= MessageFormat.format(format, new Object[] { Integer.toString(PlatformUI.getWorkbench().getProgressService().getLongOperationTime()/1000) } );
 			MessageDialog.openError(fComposite.getShell(), title, msg);
-			e= null;
-		} catch (InterruptedException ex) {
-			// 
 		}
-					
-		if (e == null) {
-			// 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());	
-				
-			newAllDiffs.add(diff);
-		} else {
-			for (int i= 0; i < e.length; i++) {
-				String a= null, s= null, d= null;
-				RangeDifference es= e[i];
-				
-				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());
-				
-				if (isPatchHunk()) {
-					if (isRight)
-						leftStart = leftEnd = getHunkStart();
-					else
-						rightStart = rightEnd = getHunkStart();
-				}
-				
-				Diff diff= new Diff(null, kind,
-					aDoc, aRegion, ancestorStart, ancestorEnd,
-					lDoc, lRegion, leftStart, leftEnd,
-					rDoc, rRegion, rightStart, rightEnd);	
-				
-				newAllDiffs.add(diff);	// remember all range diffs for scrolling
 		
-				if (ignoreWhiteSpace && !isPatchHunk()) {
-					if (sancestor != null)
-						a= extract2(aDoc, sancestor, es.ancestorStart(), es.ancestorLength());
-					s= extract2(lDoc, sleft, es.leftStart(), es.leftLength());
-					d= extract2(rDoc, sright, es.rightStart(), es.rightLength());
-				
-					if ((a == null || a.trim().length() == 0) && s.trim().length() == 0 && d.trim().length() == 0) {
-						diff.fIsWhitespace= true;
-						continue;
-					}
-				}
-		
-				if (useChange(kind)) {
-					fChangeDiffs.add(diff);	// here we remember only the real diffs
-					updateDiffBackground(diff);
-		
-					// Only do the token diff for non-hunks
-					if (!isPatchHunk()) {
-						if (s == null)
-							s= extract2(lDoc, sleft, es.leftStart(), es.leftLength());
-						if (d == null)
-							d= extract2(rDoc, sright, es.rightStart(), es.rightLength());
-						
-						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);
-						}
-					}
-				}
+		if (fMerger.hasChanges()) {
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
+				updateDiffBackground(diff);
 			}
 		}
-		
-		if (!fSubDoc && rRegion != null && lRegion != null) {
-			// we have to add a diff for the ignored lines
-			
-			int aEnd= 0;
-			int aLen= 0;
-			if (aRegion != null && aDoc != null) {
-				aEnd= aRegion.getOffset()+aRegion.getLength();
-				aLen= aDoc.getLength();
-			}
-			Diff diff= new Diff(null, RangeDifference.NOCHANGE,
-				aDoc, aRegion, aEnd, aLen,
-				lDoc, lRegion, lRegion.getOffset()+lRegion.getLength(), lDoc.getLength(),
-				rDoc, rRegion, rRegion.getOffset()+rRegion.getLength(), rDoc.getLength());
-			newAllDiffs.add(diff);
-		}
-		fAllDiffs = newAllDiffs;
 		invalidateTextPresentation();
 	}
 	
+	private Diff findDiff(char type, int pos) {
+		try {
+			return fMerger.findDiff(type, pos);
+		} catch (CoreException e) {
+			CompareUIPlugin.log(e.getStatus());
+			String title= Utilities.getString(getResourceBundle(), "tooComplexError.title"); //$NON-NLS-1$
+			String format= Utilities.getString(getResourceBundle(), "tooComplexError.format"); //$NON-NLS-1$
+			String msg= MessageFormat.format(format, new Object[] { Integer.toString(PlatformUI.getWorkbench().getProgressService().getLongOperationTime()/1000) } );
+			MessageDialog.openError(fComposite.getShell(), title, msg);
+			return null;
+		}
+	}
+	
 	private void resetPositions(IDocument doc) {
 		if (doc == null)
 			return;
@@ -3287,314 +2788,6 @@
 		}
 		doc.addPositionCategory(DIFF_RANGE_CATEGORY);
 	}
-
-	private Diff findDiff(char type, int pos) {
-								
-		IDocument aDoc= null;
-		IDocument lDoc= fLeft.getDocument();
-		IDocument rDoc= fRight.getDocument();
-		if (lDoc == null || rDoc == null)
-			return null;
-			
-		Position aRegion= null;
-		Position lRegion= null;
-		Position rRegion= null;
-				
-		boolean threeWay= isThreeWay();
-		
-		if (threeWay && !isIgnoreAncestor())
-			aDoc= fAncestor.getDocument();
-
-		boolean ignoreWhiteSpace= Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false);		
-		
-		DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace);
-		DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace);
-		DocLineComparator sancestor= null;
-		if (aDoc != null)
-			sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
-			
-		final ResourceBundle bundle= getResourceBundle();
-			
-		final Object[] result= new Object[1];
-		final DocLineComparator sa= sancestor, sl= sleft, sr= sright;
-		IRunnableWithProgress runnable= new IRunnableWithProgress() {
-			public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
-				String progressTitle= Utilities.getString(bundle, "compareProgressTask.title"); //$NON-NLS-1$
-				monitor.beginTask(progressTitle, 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();
-			}
-		};
-		IProgressService progressService= PlatformUI.getWorkbench().getProgressService();
-		
-		RangeDifference[] e= null;
-		try {
-			progressService.run(true, true, runnable);
-			e= (RangeDifference[]) result[0];
-		} catch (InvocationTargetException ex) {
-			String title= Utilities.getString(bundle, "tooComplexError.title"); //$NON-NLS-1$
-			String format= Utilities.getString(bundle, "tooComplexError.format"); //$NON-NLS-1$
-			String msg= MessageFormat.format(format, new Object[] { Integer.toString(progressService.getLongOperationTime()/1000) } );
-			MessageDialog.openError(fComposite.getShell(), title, msg);
-			e= null;
-		} catch (InterruptedException ex) {
-			// 
-		}
-					
-		if (e != null) {
-			for (int i= 0; i < e.length; i++) {
-				RangeDifference es= e[i];
-				
-				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;
-	}
-	
-	/*
-	 * Returns true if kind of change should be shown.
-	 */
-	private boolean useChange(int kind) {
-		if (kind == RangeDifference.NOCHANGE)
-			return false;
-		if (kind == RangeDifference.ANCESTOR)
-			return fShowPseudoConflicts;
-		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$
-	}
-
-	/*
-	 * 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 (int i= 0; i < e.length; i++) {
-			RangeDifference es= e[i];
-			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);
-				}
-			}
-		}
-	}
-	
-	/*
-	 * 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);
-			}
-		}
-	}
 	
 	//---- update UI stuff
 	
@@ -3629,16 +2822,8 @@
 		if (fCopyDiffRightToLeftItem != null)
 			((Action)fCopyDiffRightToLeftItem.getAction()).setEnabled(rightToLeft);
 			
-		boolean enableNavigation= false;
-		if (fCurrentDiff == null && fChangeDiffs != null && fChangeDiffs.size() > 0)
-			enableNavigation= true;
-		else if (fChangeDiffs != null && fChangeDiffs.size() > 1)
-			enableNavigation= true;
-		else if (fCurrentDiff != null && fCurrentDiff.fDiffs != null)
-			enableNavigation= true;
-		else if (fCurrentDiff != null && fCurrentDiff.fIsToken)
-			enableNavigation= true;
-		
+		boolean enableNavigation= isNavigationPossible();
+
 		if (fNextDiff != null) {
 			IAction a= fNextDiff.getAction();
 			a.setEnabled(enableNavigation || hasNextElement(true));
@@ -3667,14 +2852,14 @@
 			int unresolvedIncoming= 0;
 			int unresolvedConflicting= 0;
 
-			if (fChangeDiffs != null) {
-				Iterator e= fChangeDiffs.iterator();
-				while (e.hasNext()) {
-					Diff d= (Diff) e.next();
+			if (fMerger.hasChanges()) {
+				for (Iterator iterator = fMerger.changesIterator(); iterator
+						.hasNext();) {
+					Diff d = (Diff) iterator.next();
 					if (d.isIncomingOrConflicting() /* && useChange(d.fDirection) && !d.fIsWhitespace */) {
 						incomingOrConflicting++;
-						if (!d.fResolved) {
-							if (d.fDirection == RangeDifference.CONFLICT) {
+						if (!d.isResolved()) {
+							if (d.getKind() == RangeDifference.CONFLICT) {
 								unresolvedConflicting++;
 								break; // we can stop here because a conflict has the maximum priority
 							}
@@ -3709,16 +2894,16 @@
 			diffDescription= CompareMessages.TextMergeViewer_diffDescription_noDiff_format;	
 		} else {
 			
-			if (diff.fIsToken)		// we don't show special info for token diffs
-				diff= diff.fParent;
+			if (diff.isToken())		// we don't show special info for token diffs
+				diff= diff.getParent();
 		
 			String format= CompareMessages.TextMergeViewer_diffDescription_diff_format;	
 			diffDescription= MessageFormat.format(format, 
 				new String[] {
 					getDiffType(diff),						// 0: diff type
 					getDiffNumber(diff),					// 1: diff number
-					getDiffRange(fLeft, diff.fLeftPos),		// 2: left start line
-					getDiffRange(fRight, diff.fRightPos)	// 3: left end line
+					getDiffRange(fLeft, diff.getPosition(LEFT_CONTRIBUTOR)),		// 2: left start line
+					getDiffRange(fRight, diff.getPosition(RIGHT_CONTRIBUTOR))	// 3: left end line
 				}
 			);
 		}
@@ -3741,7 +2926,7 @@
 	
 	private String getDiffType(Diff diff) {
 		String s= ""; 	//$NON-NLS-1$
-		switch(diff.fDirection) {
+		switch(diff.getKind()) {
 		case RangeDifference.LEFT:
 			s= CompareMessages.TextMergeViewer_direction_outgoing;	
 			break;
@@ -3759,10 +2944,9 @@
 	private String getDiffNumber(Diff diff) {
 		// find the diff's number
 		int diffNumber= 0;
-		if (fChangeDiffs != null) {
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff d= (Diff) e.next();
+		if (fMerger.hasChanges()) {
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff d = (Diff) iterator.next();
 				diffNumber++;
 				if (d == diff)
 					break;
@@ -4036,9 +3220,9 @@
 		
 		Diff firstDiff= null;
 		if (first)
-			firstDiff= findNext(fRight, fChangeDiffs, -1, -1, false);
+			firstDiff= findNext(fRight, -1, -1, false);
 		else
-			firstDiff= findPrev(fRight, fChangeDiffs, 9999999, 9999999, false);
+			firstDiff= findPrev(fRight, 9999999, 9999999, false);
 		setCurrentDiff(firstDiff, true);
 	}
 	
@@ -4189,26 +3373,25 @@
 
 		boolean showResolveUI= showResolveUI();
 
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			int lshift= fLeft.getVerticalScrollOffset();
 			int rshift= fRight.getVerticalScrollOffset();
 					
 			Point region= new Point(0, 0);
 		
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff diff= (Diff) e.next();
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
 				if (diff.isDeleted())
 					continue;
 				
 				if (fShowCurrentOnly2 && !isCurrentDiff(diff))
 					continue;
 
-				fLeft.getLineRange(diff.fLeftPos, region);
+				fLeft.getLineRange(diff.getPosition(LEFT_CONTRIBUTOR), region);
 				int ly= (region.x * lineHeightLeft) + lshift;
 				int lh= region.y * lineHeightLeft;
 	
-				fRight.getLineRange(diff.fRightPos, region);
+				fRight.getLineRange(diff.getPosition(RIGHT_CONTRIBUTOR), region);
 				int ry= (region.x * lineHeightRight) + rshift;
 				int rh= region.y * lineHeightRight;
 	
@@ -4336,20 +3519,20 @@
 		if (! fHighlightRanges)
 			return;
 
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			int shift= tp.getVerticalScrollOffset() + (2-LW);
 				
 			Point region= new Point(0, 0);
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff diff= (Diff) e.next();
+			char leg = getLeg(tp);
+			for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
 				if (diff.isDeleted())
 					continue;
 				
 				if (fShowCurrentOnly2 && !isCurrentDiff(diff))
 					continue;
 
-				tp.getLineRange(diff.getPosition(tp), region);	
+				tp.getLineRange(diff.getPosition(leg), region);	
 				int y= (region.x * lineHeight) + shift;
 				int h= region.y * lineHeight;
 	
@@ -4378,7 +3561,7 @@
 		
 		if (! fHighlightRanges)
 			return;
-		if (fChangeDiffs == null)
+		if (!fMerger.hasChanges())
 			return;
 
 		Control canvas= (Control) event.widget;
@@ -4395,17 +3578,17 @@
 			shift+= fTopInset;
 				
 		Point range= new Point(0, 0);
-				
-		Iterator e= fChangeDiffs.iterator();	
-		while (e.hasNext()) {
-			Diff diff= (Diff) e.next();
+		
+		char leg = getLeg(tp);
+		for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+			Diff diff = (Diff) iterator.next();
 			if (diff.isDeleted())
 				continue;
 			
 			if (fShowCurrentOnly && !isCurrentDiff(diff))
 				continue;
 
-			tp.getLineRange(diff.getPosition(tp), range);
+			tp.getLineRange(diff.getPosition(leg), range);
 			int y= (range.x * lineHeight) + shift;
 			int h= range.y * lineHeight;
 			
@@ -4421,10 +3604,10 @@
 	}
 
 	private RGB getFillColor(Diff diff) {
-		boolean selected= fCurrentDiff != null && fCurrentDiff.fParent == diff;
+		boolean selected= fCurrentDiff != null && fCurrentDiff.getParent() == diff;
 		RGB selected_fill= getBackground(null);
 		if (isThreeWay() && !isIgnoreAncestor()) {
-			switch (diff.fDirection) {
+			switch (diff.getKind()) {
 			case RangeDifference.RIGHT:
 				if (fLeftIsLocal)
 					return selected ? selected_fill : INCOMING_FILL;
@@ -4444,10 +3627,10 @@
 	}
 	
 	private RGB getStrokeColor(Diff diff) {
-		boolean selected= fCurrentDiff != null && fCurrentDiff.fParent == diff;
+		boolean selected= fCurrentDiff != null && fCurrentDiff.getParent() == diff;
 		
 		if (isThreeWay() && !isIgnoreAncestor()) {
-			switch (diff.fDirection) {
+			switch (diff.getKind()) {
 			case RangeDifference.RIGHT:
 				if (fLeftIsLocal)
 					return selected ? SELECTED_INCOMING : INCOMING;
@@ -4501,12 +3684,13 @@
 		if (part == null)
 			return null;
 		Point s = part.getSelectedRange();
+		char leg = getLeg(part);
 		for (;;) {
 			diff = null;
 			diff = internalGetNextDiff(down, deep, part, s);
-			if (diff != null && diff.fDirection == RangeDifference.ANCESTOR
+			if (diff != null && diff.getKind() == RangeDifference.ANCESTOR
 					&& !isAncestorVisible()) {
-				Position position = diff.getPosition(part);
+				Position position = diff.getPosition(leg);
 				s = new Point(position.getOffset(), position.getLength());
 				diff= null;
 				continue;
@@ -4517,10 +3701,10 @@
 	}
 	
 	private Diff internalGetNextDiff(boolean down, boolean deep, MergeSourceViewer part, Point s) {
-		if (fChangeDiffs != null) {
+		if (fMerger.hasChanges()) {
 			if (down)
-				return findNext(part, fChangeDiffs, s.x, s.x+s.y, deep);
-			return findPrev(part, fChangeDiffs, s.x, s.x+s.y, deep);						
+				return findNext(part, s.x, s.x+s.y, deep);
+			return findPrev(part, s.x, s.x+s.y, deep);						
 		}
 		return null;
 	}
@@ -4533,12 +3717,7 @@
 	}
 
 	private Diff getWrappedDiff(Diff diff, boolean down) {
-		if (fChangeDiffs != null && fChangeDiffs.size() > 0) {
-			if (down)
-				return (Diff) fChangeDiffs.get(0);
-			return (Diff) fChangeDiffs.get(fChangeDiffs.size()-1);
-		}
-		return null;
+		return fMerger.getWrappedDiff(diff, down);
 	}
 	
 	/*
@@ -4558,7 +3737,7 @@
 			}
 			if (diff != null)
 				setCurrentDiff(diff, true, deep);
-			if (diff != null && diff.fDirection == RangeDifference.ANCESTOR
+			if (diff != null && diff.getKind() == RangeDifference.ANCESTOR
 					&& !isAncestorVisible())
 				continue;
 			break;
@@ -4662,83 +3841,16 @@
 	 * is returned.
 	 */
 	private Diff findDiff(MergeSourceViewer tp, int rangeStart, int rangeEnd) {
-		if (fChangeDiffs != null) {
-			Iterator e= fChangeDiffs.iterator();
-			while (e.hasNext()) {
-				Diff diff= (Diff) e.next();
-				if (diff.overlaps(tp, rangeStart, rangeEnd))
-					return diff;
-			}
-		}
-		return null;
+		char contributor = getLeg(tp);
+		return fMerger.findDiff(contributor, rangeStart, rangeEnd);
 	}
 	
-	private static Diff findNext(MergeSourceViewer tp, List v, int start, int end, boolean deep) {
-		for (int i= 0; i < v.size(); i++) {
-			Diff diff= (Diff) v.get(i);
-			Position p= diff.getPosition(tp);
-			if (p != null) {
-				int startOffset= p.getOffset();
-				if (end < startOffset)  // <=
-					return diff;
-				if (deep && diff.fDiffs != null) {
-					Diff d= null;
-					int endOffset= startOffset + p.getLength();
-					if (start == startOffset && (end == endOffset || end == endOffset-1)) {
-						d= findNext(tp, diff.fDiffs, start-1, start-1, deep);
-					} else if (end < endOffset) {
-						d= findNext(tp, diff.fDiffs, start, end, deep);
-					}
-					if (d != null)
-						return d;
-				}
-			}
-		}
-		return null;
+	private Diff findNext(MergeSourceViewer tp, int start, int end, boolean deep) {
+		return fMerger.findNext(getLeg(tp), start, end, deep);
 	}
 	
-	private static Diff findPrev(MergeSourceViewer tp, List v, int start, int end, boolean deep) {
-		for (int i= v.size()-1; i >= 0; i--) {
-			Diff diff= (Diff) v.get(i);
-			Position p= diff.getPosition(tp);
-			if (p != null) {
-				int startOffset= p.getOffset();
-				int endOffset= startOffset + p.getLength();
-				if (start > endOffset) {
-					if (deep && diff.fDiffs != null) {
-						// If we are going deep, find the last change in the diff
-						return findPrev(tp, diff.fDiffs, end, end, deep);
-					}
-					return diff;
-				}
-				if (deep && diff.fDiffs != null) {
-					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 (isFirstDiff(tp, startOffset, diff.fDiffs)) {
-							return diff;
-						}
-						d= findPrev(tp, diff.fDiffs, start, end, deep);
-					}
-					if (d != null)
-						return d;
-				}
-			}
-		}
-		return null;
-	}
-		
-	private static boolean isFirstDiff(MergeSourceViewer tp, int startOffset,
-			ArrayList diffs) {
-		if (diffs.isEmpty())
-			return false;
-		Diff diff = (Diff)diffs.get(0);
-		Position p= diff.getPosition(tp);
-		return (p.getOffset() >= startOffset);
+	private Diff findPrev(MergeSourceViewer tp, int start, int end, boolean deep) {
+		return fMerger.findPrev(getLeg(tp), start, end, deep);
 	}
 
 	/*
@@ -4770,27 +3882,27 @@
 			// before we set fCurrentDiff we change the selection
 			// so that the paint code uses the old background colors
 			// otherwise selection isn't drawn correctly
-			if (d.fIsToken || !fHighlightTokenChanges || deep || !d.hasChildren()) {
+			if (d.isToken() || !fHighlightTokenChanges || deep || !d.hasChildren()) {
 				if (isThreeWay() && !isIgnoreAncestor())
-					fAncestor.setSelection(d.fAncestorPos);
-				fLeft.setSelection(d.fLeftPos);
-				fRight.setSelection(d.fRightPos);
+					fAncestor.setSelection(d.getPosition(ANCESTOR_CONTRIBUTOR));
+				fLeft.setSelection(d.getPosition(LEFT_CONTRIBUTOR));
+				fRight.setSelection(d.getPosition(RIGHT_CONTRIBUTOR));
 			} else {
 				if (isThreeWay() && !isIgnoreAncestor())
-					fAncestor.setSelection(new Position(d.fAncestorPos.offset, 0));
-				fLeft.setSelection(new Position(d.fLeftPos.offset, 0));
-				fRight.setSelection(new Position(d.fRightPos.offset, 0));
+					fAncestor.setSelection(new Position(d.getPosition(ANCESTOR_CONTRIBUTOR).offset, 0));
+				fLeft.setSelection(new Position(d.getPosition(LEFT_CONTRIBUTOR).offset, 0));
+				fRight.setSelection(new Position(d.getPosition(RIGHT_CONTRIBUTOR).offset, 0));
 			}
 			
 			// now switch diffs
 			fCurrentDiff= d;
-			revealDiff(d, d.fIsToken);
+			revealDiff(d, d.isToken());
 		} else {
 			fCurrentDiff= d;
 		}
 		
-		Diff d1= oldDiff != null ? oldDiff.fParent : null;
-		Diff d2= fCurrentDiff != null ? fCurrentDiff.fParent : null;
+		Diff d1= oldDiff != null ? oldDiff.getParent() : null;
+		Diff d2= fCurrentDiff != null ? fCurrentDiff.getParent() : null;
 		if (d1 != d2) {
 			updateDiffBackground(d1);
 			updateDiffBackground(d2);
@@ -4813,11 +3925,11 @@
 		if (smart) {
 			Point region= new Point(0, 0);
 			// find the starting line of the diff in all text widgets
-			int ls= fLeft.getLineRange(d.fLeftPos, region).x;
-			int rs= fRight.getLineRange(d.fRightPos, region).x;
+			int ls= fLeft.getLineRange(d.getPosition(LEFT_CONTRIBUTOR), region).x;
+			int rs= fRight.getLineRange(d.getPosition(RIGHT_CONTRIBUTOR), region).x;
 			
 			if (isThreeWay() && !isIgnoreAncestor()) {
-				int as= fAncestor.getLineRange(d.fAncestorPos, region).x;
+				int as= fAncestor.getLineRange(d.getPosition(ANCESTOR_CONTRIBUTOR), region).x;
 				if (as >= fAncestor.getTopIndex() && as <= fAncestor.getBottomIndex())
 					ancestorIsVisible= true;
 			}
@@ -4835,33 +3947,31 @@
 			
 			MergeSourceViewer allButThis= null;
 			if (leftIsVisible) {
-				avpos= lvpos= rvpos= realToVirtualPosition(fLeft, fLeft.getTopIndex());
+				avpos= lvpos= rvpos= realToVirtualPosition(LEFT_CONTRIBUTOR, fLeft.getTopIndex());
 				allButThis= fLeft;
 			} else if (rightIsVisible) {
-				avpos= lvpos= rvpos= realToVirtualPosition(fRight, fRight.getTopIndex());
+				avpos= lvpos= rvpos= realToVirtualPosition(RIGHT_CONTRIBUTOR, fRight.getTopIndex());
 				allButThis= fRight;
 			} else if (ancestorIsVisible) {
-				avpos= lvpos= rvpos= realToVirtualPosition(fAncestor, fAncestor.getTopIndex());
+				avpos= lvpos= rvpos= realToVirtualPosition(ANCESTOR_CONTRIBUTOR, fAncestor.getTopIndex());
 				allButThis= fAncestor;
 			} else {
-				if (fAllDiffs != null) {
-					int vpos= 0;
-					Iterator e= fAllDiffs.iterator();
-					for (int i= 0; e.hasNext(); i++) {
-						Diff diff= (Diff) e.next();
-						if (diff == d)
-							break;
-						if (fSynchronizedScrolling) {
-							vpos+= diff.getMaxDiffHeight();
-						} else {
-							avpos+= diff.getAncestorHeight();
-							lvpos+= diff.getLeftHeight();
-							rvpos+= diff.getRightHeight();
-						}
+				int vpos= 0;
+				for (Iterator iterator = fMerger.rangesIterator(); iterator
+						.hasNext();) {
+					Diff diff = (Diff) iterator.next();
+					if (diff == d)
+						break;
+					if (fSynchronizedScrolling) {
+						vpos+= diff.getMaxDiffHeight();
+					} else {
+						avpos+= diff.getAncestorHeight();
+						lvpos+= diff.getLeftHeight();
+						rvpos+= diff.getRightHeight();
 					}
-					if (fSynchronizedScrolling)
-						avpos= lvpos= rvpos= vpos;
 				}
+				if (fSynchronizedScrolling)
+					avpos= lvpos= rvpos= vpos;
 				int delta= fRight.getViewportLines()/4;
 				avpos-= delta;
 				if (avpos < 0)
@@ -4881,11 +3991,11 @@
 		}
 		
 		// horizontal scrolling
-		if (d.fIsToken) {
+		if (d.isToken()) {
 			// we only scroll horizontally for token diffs
-			reveal(fAncestor, d.fAncestorPos);
-			reveal(fLeft, d.fLeftPos);
-			reveal(fRight, d.fRightPos);
+			reveal(fAncestor, d.getPosition(ANCESTOR_CONTRIBUTOR));
+			reveal(fLeft, d.getPosition(LEFT_CONTRIBUTOR));
+			reveal(fRight, d.getPosition(RIGHT_CONTRIBUTOR));
 		} else {
 			// in all other cases we reset the horizontal offset
 			hscroll(fAncestor);
@@ -4916,14 +4026,13 @@
 	//--------------------------------------------------------------------------------
 	
 	void copyAllUnresolved(boolean leftToRight) {
-		if (fChangeDiffs != null && isThreeWay() && !isIgnoreAncestor()) {
+		if (fMerger.hasChanges() && isThreeWay() && !isIgnoreAncestor()) {
 			IRewriteTarget target= leftToRight ? fRight.getRewriteTarget() : fLeft.getRewriteTarget();
 			boolean compoundChangeStarted= false;
-			Iterator e= fChangeDiffs.iterator();
 			try {
-				while (e.hasNext()) {
-					Diff diff= (Diff) e.next();
-					switch (diff.fDirection) {
+				for (Iterator iterator = fMerger.changesIterator(); iterator.hasNext();) {
+					Diff diff = (Diff) iterator.next();
+					switch (diff.getKind()) {
 					case RangeDifference.LEFT:
 						if (leftToRight) {
 							if (!compoundChangeStarted) {
@@ -5029,66 +4138,15 @@
 		if (diff != null && !diff.isResolved()) {
 			if (!validateChange(!leftToRight)) 
 				return false;
-			Position fromPos= null;
-			Position toPos= null;
-			IDocument fromDoc= null;
-			IDocument toDoc= null;
-
 			if (leftToRight) {
 				fRight.setEnabled(true);
-				fromPos= diff.fLeftPos;
-				toPos= diff.fRightPos;
-				fromDoc= fLeft.getDocument();
-				toDoc= fRight.getDocument();
 			} else {
 				fLeft.setEnabled(true);
-				fromPos= diff.fRightPos;
-				toPos= diff.fLeftPos;
-				fromDoc= fRight.getDocument();
-				toDoc= fLeft.getDocument();
 			}
-			
-			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.fDirection) {
-					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);
-							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);
-			updateResolveStatus();
-			
-			return true;
+			boolean result = fMerger.copy(diff, leftToRight);
+			if (result)
+				updateResolveStatus();
+			return result;
 		}
 		return false;
 	}
@@ -5104,36 +4162,6 @@
 	}
 
 	//---- scrolling
-
-	/*
-	 * Calculates virtual height (in lines) of views by adding the maximum of corresponding diffs.
-	 */
-	private int getVirtualHeight() {
-		int h= 1;
-		if (fAllDiffs != null) {
-			Iterator e= fAllDiffs.iterator();
-			for (int i= 0; e.hasNext(); i++) {
-				Diff diff= (Diff) e.next();
-				h+= diff.getMaxDiffHeight();
-			}
-		}
-		return h;
-	}
-	
-	/*
-	 * Calculates height (in lines) of right view by adding the height of the right diffs.
-	 */
-	private int getRightHeight() {
-		int h= 1;
-		if (fAllDiffs != null) {
-			Iterator e= fAllDiffs.iterator();
-			for (int i= 0; e.hasNext(); i++) {
-				Diff diff= (Diff) e.next();
-				h+= diff.getRightHeight();
-			}
-		}
-		return h;
-	}
 	
 	/*
 	 * The height of the TextEditors in lines.
@@ -5160,35 +4188,10 @@
 	/*
 	 * Returns the virtual position for the given view position.
 	 */
-	private int realToVirtualPosition(MergeSourceViewer w, int vpos) {
-
-		if (! fSynchronizedScrolling || fAllDiffs == null)
+	private int realToVirtualPosition(char contributor, int vpos) {
+		if (! fSynchronizedScrolling)
 			return vpos;
-				
-		int viewPos= 0;		// real view position
-		int virtualPos= 0;	// virtual position
-		Point region= new Point(0, 0);
-		
-		Iterator e= fAllDiffs.iterator();
-		while (e.hasNext()) {
-			Diff diff= (Diff) e.next();
-			Position pos= diff.getPosition(w);
-			w.getLineRange(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;
+		return fMerger.realToVirtualPosition(contributor, vpos);
 	}
 		
 	private void scrollVertical(int avpos, int lvpos, int rvpos, MergeSourceViewer allBut) {
@@ -5196,7 +4199,7 @@
 		int s= 0;
 		
 		if (fSynchronizedScrolling) {
-			s= getVirtualHeight() - rvpos;
+			s= fMerger.getVirtualHeight() - rvpos;
 			int height= fRight.getViewportLines()/4;
 			if (s < 0)
 				s= 0;
@@ -5208,21 +4211,21 @@
 				
 		if (isThreeWay() && allBut != fAncestor) {
 			if (fSynchronizedScrolling || allBut == null) {
-				int y= virtualToRealPosition(fAncestor, avpos+s)-s;
+				int y= virtualToRealPosition(ANCESTOR_CONTRIBUTOR, avpos+s)-s;
 				fAncestor.vscroll(y);
 			}
 		}
 
 		if (allBut != fLeft) {
 			if (fSynchronizedScrolling || allBut == null) {
-				int y= virtualToRealPosition(fLeft, lvpos+s)-s;
+				int y= virtualToRealPosition(LEFT_CONTRIBUTOR, lvpos+s)-s;
 				fLeft.vscroll(y);
 			}
 		}
 
 		if (allBut != fRight) {
 			if (fSynchronizedScrolling || allBut == null) {
-				int y= virtualToRealPosition(fRight, rvpos+s)-s;
+				int y= virtualToRealPosition(RIGHT_CONTRIBUTOR, rvpos+s)-s;
 				fRight.vscroll(y);
 			}
 		}
@@ -5254,12 +4257,12 @@
 		int ix= w.getTopIndex();
 		int ix2= w.getDocumentRegionOffset();
 		
-		int viewPosition= realToVirtualPosition(w, ix-ix2);
+		int viewPosition= realToVirtualPosition(getLeg(w), ix-ix2);
 				
 		scrollVertical(viewPosition, viewPosition, viewPosition, w);	// scroll all but the given views
 		
 		if (fVScrollBar != null) {
-			int value= Math.max(0, Math.min(viewPosition, getVirtualHeight() - getViewportHeight()));
+			int value= Math.max(0, Math.min(viewPosition, fMerger.getVirtualHeight() - getViewportHeight()));
 			fVScrollBar.setSelection(value);
 			//refreshBirdEyeView();
 		}
@@ -5270,7 +4273,7 @@
 	private void updateVScrollBar() {
 		
 		if (Utilities.okToUse(fVScrollBar) && fSynchronizedScrolling) {
-			int virtualHeight= getVirtualHeight();
+			int virtualHeight= fMerger.getVirtualHeight();
 			int viewPortHeight= getViewportHeight();
 			int pageIncrement= viewPortHeight-1;
 			int thumb= (viewPortHeight > virtualHeight) ? virtualHeight : viewPortHeight;
@@ -5284,34 +4287,10 @@
 	/*
 	 * maps given virtual position into a real view position of this view.
 	 */
-	private int virtualToRealPosition(MergeSourceViewer part, int v) {
-			
-		if (! fSynchronizedScrolling || fAllDiffs == null)
+	private int virtualToRealPosition(char contributor, int v) {
+		if (! fSynchronizedScrolling)
 			return v;
-					
-		int virtualPos= 0;
-		int viewPos= 0;
-		Point region= new Point(0, 0);
-		
-		Iterator e= fAllDiffs.iterator();
-		while (e.hasNext()) {
-			Diff diff= (Diff) e.next();
-			Position pos= diff.getPosition(part);
-			int viewHeight= part.getLineRange(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;
+		return fMerger.virtualToRealPosition(contributor, v);
 	}
 	
 	/* (non-Javadoc)
@@ -5453,8 +4432,7 @@
 	private void resetDiffs() {
 		// clear stuff
 		fCurrentDiff= null;
-		fChangeDiffs= null;
-		fAllDiffs= null;
+		fMerger.reset();
 		resetPositions(fLeft.getDocument());
 		resetPositions(fRight.getDocument());
 		resetPositions(fAncestor.getDocument());
@@ -5492,4 +4470,36 @@
 			fFindReplaceTarget= new FindReplaceTarget();
 		return fFindReplaceTarget;
 	}
+	
+	/* package */ char getLeg(MergeSourceViewer w) {
+		if (w == fLeft)
+			return LEFT_CONTRIBUTOR;
+		if (w == fRight)
+			return RIGHT_CONTRIBUTOR;
+		if (w == fAncestor)
+			return ANCESTOR_CONTRIBUTOR;
+		return ANCESTOR_CONTRIBUTOR;
+	}
+	
+	public boolean isCurrentDiff(Diff diff) {
+		if (diff == null)
+			return false;
+		if (diff == fCurrentDiff)
+			return true;
+		if (fCurrentDiff != null && fCurrentDiff.getParent() == diff)
+			return true;
+		return false;
+	}
+	
+	public boolean isNavigationPossible() {
+		if (fCurrentDiff == null && fMerger.hasChanges())
+			return true;
+		else if (fMerger.changesCount() > 1)
+			return true;
+		else if (fCurrentDiff != null && fCurrentDiff.hasChildren())
+			return true;
+		else if (fCurrentDiff != null && fCurrentDiff.isToken())
+			return true;
+		return false;
+	}
 }
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java
new file mode 100644
index 0000000..6429090
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare/compare/org/eclipse/compare/internal/merge/DocumentMerger.java
@@ -0,0 +1,1338 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.merge;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.contentmergeviewer.ITokenComparator;
+import org.eclipse.compare.internal.*;
+import org.eclipse.compare.rangedifferencer.*;
+import org.eclipse.compare.structuremergeviewer.Differencer;
+import org.eclipse.core.runtime.*;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.text.*;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.progress.IProgressService;
+
+/**
+ * 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 fAllDiffs;
+	/** Subset of above: just real differences. */
+	private ArrayList fChangeDiffs;
+	
+	private final boolean fLeftIsLocal;
+
+	private IDocumentMergerInput fInput;
+	
+	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();
+		
+	}
+	
+	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 */
+		ArrayList 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+= Differencer.LEFT;
+				break;
+			case RangeDifference.LEFT:
+				code+= 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 e= fDiffs.iterator();
+				while (e.hasNext()) {
+					Diff d= (Diff) e.next();
+					if (!d.isResolved())
+						return false;
+				}
+				return true;
+			}
+			return fResolved;
+		}
+		
+//		private boolean isIncoming() {
+//			switch (fDirection) {
+//			case RangeDifference.RIGHT:
+//				if (fLeftIsLocal)
+//					return true;
+//				break;
+//			case RangeDifference.LEFT:
+//				if (!fLeftIsLocal)
+//					return true;
+//				break;
+//			}
+//			return false;
+//		}
+		
+		public boolean isIncomingOrConflicting() {
+			switch (fDirection) {
+			case RangeDifference.RIGHT:
+				if (fLeftIsLocal)
+					return true;
+				break;
+			case RangeDifference.LEFT:
+				if (!fLeftIsLocal)
+					return true;
+				break;
+			case RangeDifference.CONFLICT:
+				return true;
+			}
+			return false;
+		}
+		
+//		private boolean isUnresolvedIncoming() {
+//			if (fResolved)
+//				return false;
+//			return isIncoming();
+//		}
+		
+		public boolean isUnresolvedIncomingOrConflicting() {
+			if (fResolved)
+				return false;
+			return isIncomingOrConflicting();
+		}
+				
+		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) {
+			Position h= getPosition(contributor);
+			if (h != null) {
+				int ds= h.getOffset();
+				int de= ds + h.getLength();
+				if ((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 result = new ArrayList();
+				for (Iterator iterator = fDiffs.iterator(); iterator.hasNext();) {
+					Diff diff = (Diff) iterator.next();
+					if (diff.intersectsRegion(contributor, region)) {
+						result.add(diff);
+					}
+				}
+				return (Diff[]) 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 childIterator() {
+			if (fDiffs == null)
+				return new ArrayList().iterator();
+			return fDiffs.iterator();
+		}
+	}
+	
+	public DocumentMerger(IDocumentMergerInput input) {
+		this.fInput = input;
+		fLeftIsLocal= Utilities.getBoolean(getCompareConfiguration(), "LEFT_IS_LOCAL", false); //$NON-NLS-1$
+	}
+	
+	/**
+	 * 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();		
+		
+		DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace);
+		DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace);
+		DocLineComparator sancestor= null;
+		if (aDoc != null) {
+			sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
+			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= new IRunnableWithProgress() {
+			public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
+				monitor.beginTask("Computing Differences...", 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, "Too many differences found", ex.getTargetException()));
+		} catch (InterruptedException ex) {
+			// 
+		}
+		
+		ArrayList newAllDiffs = new ArrayList();
+		for (int i= 0; i < e.length; i++) {
+			RangeDifference es= e[i];
+			
+			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;
+	}
+
+	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();
+		
+		DocLineComparator sright= new DocLineComparator(rDoc, toRegion(rRegion), ignoreWhiteSpace);
+		DocLineComparator sleft= new DocLineComparator(lDoc, toRegion(lRegion), ignoreWhiteSpace);
+		DocLineComparator sancestor= null;
+		if (aDoc != null)
+			sancestor= new DocLineComparator(aDoc, toRegion(aRegion), ignoreWhiteSpace);
+			
+		final Object[] result= new Object[1];
+		final DocLineComparator sa= sancestor, sl= sleft, sr= sright;
+		IRunnableWithProgress runnable= new IRunnableWithProgress() {
+			public void run(IProgressMonitor monitor) throws InterruptedException, InvocationTargetException {
+				monitor.beginTask("Finding Differences...", 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();
+			}
+		};
+		IProgressService progressService= PlatformUI.getWorkbench().getProgressService();
+		
+		RangeDifference[] e= null;
+		try {
+			progressService.run(true, true, runnable);
+			e= (RangeDifference[]) result[0];
+		} catch (InvocationTargetException ex) {
+			throw new CoreException(new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, 0, "Too many differences found", ex.getTargetException()));
+		} catch (InterruptedException ex) {
+			// 
+		}
+					
+		if (e != null) {
+			for (int i= 0; i < e.length; i++) {
+				RangeDifference es= e[i];
+				
+				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 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 (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 (int i= 0; i < e.length; i++) {
+			RangeDifference es= e[i];
+			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 (Iterator iterator = fAllDiffs.iterator(); iterator.hasNext();) {
+			Diff diff = (Diff) iterator.next();
+			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 e= fAllDiffs.iterator();
+		while (e.hasNext()) {
+			Diff 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 e= fAllDiffs.iterator();
+		while (e.hasNext()) {
+			Diff 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 e= fAllDiffs.iterator();
+			for (int i= 0; e.hasNext(); i++) {
+				Diff 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 e= fAllDiffs.iterator();
+			for (int i= 0; e.hasNext(); i++) {
+				Diff 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 intersectingDiffs = new ArrayList();
+		for (Iterator iterator = fChangeDiffs.iterator(); iterator.hasNext();) {
+			Diff diff = (Diff) iterator.next();
+			Diff[] changeDiffs = diff.getChangeDiffs(contributor, region);
+			for (int i = 0; i < changeDiffs.length; i++) {
+				Diff changeDiff = changeDiffs[i];
+				intersectingDiffs.add(changeDiff);
+			}
+		}
+		return (Diff[]) 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 e= fAllDiffs.iterator();
+			for (int i= 0; e.hasNext(); i++) {
+				Diff 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 changesIterator() {
+		if (fChangeDiffs == null)
+			return new ArrayList().iterator();
+		return fChangeDiffs.iterator();
+	}
+	
+	public Iterator rangesIterator() {
+		if (fAllDiffs == null)
+			return new ArrayList().iterator();
+		return fAllDiffs.iterator();
+	}
+	
+	public boolean isFirstChildDiff(char contributor, int startOffset,
+			Diff diff) {
+		if (!diff.hasChildren())
+			return false;
+		Diff d = (Diff)diff.fDiffs.get(0);
+		Position p= d.getPosition(contributor);
+		return (p.getOffset() >= startOffset);
+	}
+	
+	public Diff getWrappedDiff(Diff diff, boolean down) {
+		if (fChangeDiffs != null && fChangeDiffs.size() > 0) {
+			if (down)
+				return (Diff) fChangeDiffs.get(0);
+			return (Diff) 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 && !diff.isResolved()) {
+			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);
+							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 iterator = changesIterator(); iterator.hasNext();) {
+				Diff diff = (Diff) iterator.next();
+				if (diff.isDeleted() || diff.getKind() == RangeDifference.NOCHANGE)
+				    continue;
+				if (diff.overlaps(contributor, rangeStart, rangeEnd))
+					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 v, int start, int end, boolean deep) {
+		if (v == null)
+			return null;
+		for (int i= 0; i < v.size(); i++) {
+			Diff diff= (Diff) v.get(i);
+			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 v, int start, int end, boolean deep) {
+		if (v == null)
+			return null;
+		for (int i= v.size()-1; i >= 0; i--) {
+			Diff 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, startOffset, diff)) {
+							return diff;
+						}
+						d= findPrev(contributor, diff.fDiffs, start, end, deep);
+					}
+					if (d != null)
+						return d;
+				}
+			}
+		}
+		return null;
+	}
+	
+}